Download detector.py from Marnu25/x-tracker: direct link, hf CLI and curl.
- Browser
- Download file 27.7 kB
-
https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/detector.py
- Command line
-
hf download hf://spaces/Marnu25/x-tracker/detector.py
-
curl -L -o detector.py https://huggingface.co/spaces/Marnu25/x-tracker/resolve/main/detector.py
27.7 kB
| import ctypes | |
| import glob | |
| import os | |
| import sys | |
| import time | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple | |
| import cv2 | |
| import numpy as np | |
| # Preload NVIDIA CUDA/cuDNN pip libraries if installed so CUDAExecutionProvider works out of the box on T4 GPUs | |
| def _preload_nvidia_cuda_libs(): | |
| site_dirs = [p for p in sys.path if "site-packages" in p] | |
| lib_dirs = [] | |
| for sd in site_dirs: | |
| for cand in sorted(glob.glob(os.path.join(sd, "nvidia", "*", "lib"))): | |
| if os.path.isdir(cand): | |
| lib_dirs.append(cand) | |
| if lib_dirs: | |
| cur_ld = os.environ.get("LD_LIBRARY_PATH", "") | |
| os.environ["LD_LIBRARY_PATH"] = ":".join(lib_dirs + ([cur_ld] if cur_ld else [])) | |
| for d in lib_dirs: | |
| for so_file in sorted(glob.glob(os.path.join(d, "*.so*"))): | |
| try: | |
| ctypes.CDLL(so_file, mode=ctypes.RTLD_GLOBAL) | |
| except Exception: | |
| pass | |
| _preload_nvidia_cuda_libs() | |
| try: | |
| import onnxruntime as ort | |
| HAVE_ONNX = True | |
| except ImportError: | |
| HAVE_ONNX = False | |
| MODEL_DIR = Path(__file__).resolve().parent / "models" | |
| YOLO_MODEL_PATH = MODEL_DIR / "yolov8n.onnx" | |
| POSE_MODEL_PATH = ( | |
| MODEL_DIR / "pose" / "onnx" / "model.onnx" | |
| if (MODEL_DIR / "pose" / "onnx" / "model.onnx").exists() | |
| else MODEL_DIR / "pose" / "onnx" / "model_quantized.onnx" | |
| ) | |
| # 80 COCO Classes | |
| COCO_CLASSES = [ | |
| "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", | |
| "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", | |
| "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", | |
| "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", | |
| "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", | |
| "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", | |
| "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", | |
| "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", | |
| "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", | |
| "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush" | |
| ] | |
| # 17 COCO Keypoints | |
| KPT_NAMES = [ | |
| "nose", "left_eye", "right_eye", "left_ear", "right_ear", | |
| "left_shoulder", "right_shoulder", "left_elbow", "right_elbow", | |
| "left_wrist", "right_wrist", "left_hip", "right_hip", | |
| "left_knee", "right_knee", "left_ankle", "right_ankle" | |
| ] | |
| SKELETON_PAIRS = [ | |
| (0, 1), (0, 2), (1, 3), (2, 4), # Head / Face | |
| (5, 6), (5, 7), (7, 9), (6, 8), (8, 10), # Arms | |
| (5, 11), (6, 12), (11, 12), # Torso | |
| (11, 13), (13, 15), (12, 14), (14, 16) # Legs | |
| ] | |
| # Styling Palette | |
| COLOR_PALETTE = { | |
| "person": (50, 225, 120), # Green | |
| "head / face": (0, 235, 255), # Yellow/Gold | |
| "left hand": (255, 200, 0), # Cyan | |
| "right hand": (255, 200, 0), | |
| "left arm": (255, 175, 50), # Sky Blue | |
| "right arm": (255, 175, 50), | |
| "torso / chest": (80, 220, 120),# Emerald | |
| "chest / torso": (80, 220, 120),# Emerald | |
| "hips / pelvic": (180, 105, 255),# Coral Pink | |
| "pelvic / hips": (180, 105, 255),# Coral Pink | |
| "left leg": (255, 120, 200), # Magenta | |
| "right leg": (255, 120, 200), | |
| "left foot": (0, 165, 255), # Orange | |
| "right foot": (0, 165, 255), | |
| "cell phone": (255, 180, 0), | |
| "laptop": (255, 140, 0), | |
| "bottle": (180, 180, 180), | |
| "cup": (200, 150, 255), | |
| "chair": (255, 100, 180), | |
| "cat": (0, 180, 255), | |
| "dog": (0, 165, 255) | |
| } | |
| DEFAULT_COLOR = (0, 215, 255) | |
| def compute_box_iou(boxA: List[int], boxB: List[int]) -> float: | |
| xA = max(boxA[0], boxB[0]) | |
| yA = max(boxA[1], boxB[1]) | |
| xB = min(boxA[2], boxB[2]) | |
| yB = min(boxA[3], boxB[3]) | |
| inter = max(0, xB - xA) * max(0, yB - yA) | |
| areaA = max(0, boxA[2] - boxA[0]) * max(0, boxA[3] - boxA[1]) | |
| areaB = max(0, boxB[2] - boxB[0]) * max(0, boxB[3] - boxB[1]) | |
| union = float(areaA + areaB - inter) | |
| return inter / union if union > 0 else 0.0 | |
| class TrackedBox: | |
| def __init__(self, track_id: str, base_label: str, label: str, confidence: float, box: List[int]): | |
| self.track_id = track_id | |
| self.base_label = base_label | |
| self.label = label | |
| self.confidence = confidence | |
| self.box = list(box) | |
| self.missed_frames = 0 | |
| self.hit_count = 1 | |
| def update(self, label: str, confidence: float, new_box: List[int]): | |
| alpha = 0.60 | |
| self.label = label | |
| self.box[0] = int(self.box[0] * (1 - alpha) + new_box[0] * alpha) | |
| self.box[1] = int(self.box[1] * (1 - alpha) + new_box[1] * alpha) | |
| self.box[2] = int(self.box[2] * (1 - alpha) + new_box[2] * alpha) | |
| self.box[3] = int(self.box[3] * (1 - alpha) + new_box[3] * alpha) | |
| self.confidence = round(self.confidence * (1 - alpha) + confidence * alpha, 1) | |
| self.missed_frames = 0 | |
| self.hit_count += 1 | |
| class ObjectDetector: | |
| def __init__(self): | |
| self.yolo_session: Optional[any] = None | |
| self.pose_session: Optional[any] = None | |
| self.providers: List[str] = ["CPUExecutionProvider"] | |
| self.active_provider: str = "CPUExecutionProvider" | |
| self.gpu_active: bool = False | |
| self.loaded_yolo = False | |
| self.loaded_pose = False | |
| self.confidence_threshold = 0.45 | |
| self.nms_threshold = 0.45 | |
| # Tracking & Hysteresis | |
| self.tracked_objects: List[TrackedBox] = [] | |
| self.max_missed_frames = 3 | |
| self.next_track_num = 1 | |
| # Cache | |
| self.latest_detections: List[dict] = [] | |
| self.latest_body_parts: List[dict] = [] | |
| self.latest_keypoints: List[dict] = [] | |
| self.latest_telemetry: dict = { | |
| "gpu_active": False, | |
| "provider": "CPUExecutionProvider", | |
| "keypoints_count": 0, | |
| "objects_count": 0, | |
| "inference_ms": 0.0 | |
| } | |
| self.last_detection_time: float = 0.0 | |
| self._load_models() | |
| def _load_models(self): | |
| if not HAVE_ONNX: | |
| return | |
| opts = ort.SessionOptions() | |
| opts.intra_op_num_threads = min(4, os.cpu_count() or 2) | |
| opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL | |
| opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL | |
| available_providers = ort.get_available_providers() | |
| if "CUDAExecutionProvider" in available_providers: | |
| self.providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] | |
| else: | |
| self.providers = ["CPUExecutionProvider"] | |
| # 1. YOLOv8n Object Detection | |
| if YOLO_MODEL_PATH.exists(): | |
| try: | |
| self.yolo_session = ort.InferenceSession(str(YOLO_MODEL_PATH), opts, providers=self.providers) | |
| self.loaded_yolo = True | |
| sess_provs = self.yolo_session.get_providers() | |
| if sess_provs: | |
| self.active_provider = sess_provs[0] | |
| self.gpu_active = ("CUDA" in self.active_provider) | |
| except Exception: | |
| try: | |
| self.yolo_session = ort.InferenceSession(str(YOLO_MODEL_PATH), opts, providers=["CPUExecutionProvider"]) | |
| self.loaded_yolo = True | |
| except Exception: | |
| pass | |
| # 2. YOLOv8n-Pose (Body Parts & Keypoints) | |
| if POSE_MODEL_PATH.exists(): | |
| try: | |
| self.pose_session = ort.InferenceSession(str(POSE_MODEL_PATH), opts, providers=self.providers) | |
| self.loaded_pose = True | |
| sess_provs = self.pose_session.get_providers() | |
| if sess_provs and "CUDA" in sess_provs[0]: | |
| self.active_provider = sess_provs[0] | |
| self.gpu_active = True | |
| except Exception: | |
| try: | |
| self.pose_session = ort.InferenceSession(str(POSE_MODEL_PATH), opts, providers=["CPUExecutionProvider"]) | |
| self.loaded_pose = True | |
| except Exception: | |
| pass | |
| self.latest_telemetry["gpu_active"] = self.gpu_active | |
| self.latest_telemetry["provider"] = self.active_provider | |
| def detect(self, frame: np.ndarray) -> Tuple[List[dict], dict]: | |
| """Detects 80 COCO objects, specific body parts, and 17 pose keypoints.""" | |
| t0 = time.perf_counter() | |
| if frame is None or not HAVE_ONNX: | |
| return [], dict(self.latest_telemetry) | |
| h, w = frame.shape[:2] | |
| raw_items: List[dict] = [] | |
| skeletons: List[dict] = [] | |
| img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| resized_640 = cv2.resize(img_rgb, (640, 640), interpolation=cv2.INTER_LINEAR) | |
| blob_640 = resized_640.transpose(2, 0, 1)[None].astype(np.float32) / 255.0 | |
| x_factor = w / 640.0 | |
| y_factor = h / 640.0 | |
| # --- 1. YOLOv8 80-Class General Object Detection --- | |
| if self.loaded_yolo and self.yolo_session: | |
| try: | |
| outputs = self.yolo_session.run(['output0'], {'images': blob_640})[0][0].T | |
| boxes = outputs[:, :4] | |
| scores = outputs[:, 4:] | |
| class_ids = np.argmax(scores, axis=1) | |
| confidences = np.max(scores, axis=1) | |
| valid_mask = confidences >= self.confidence_threshold | |
| boxes = boxes[valid_mask] | |
| confidences = confidences[valid_mask] | |
| class_ids = class_ids[valid_mask] | |
| cv_boxes = [] | |
| for box in boxes: | |
| cx, cy, bw, bh = box | |
| cv_boxes.append([ | |
| int((cx - bw / 2.0) * x_factor), | |
| int((cy - bh / 2.0) * y_factor), | |
| int(bw * x_factor), | |
| int(bh * y_factor) | |
| ]) | |
| indices = cv2.dnn.NMSBoxes(cv_boxes, confidences.tolist(), self.confidence_threshold, self.nms_threshold) | |
| if len(indices) > 0: | |
| for idx in indices: | |
| i = idx if isinstance(idx, (int, np.integer)) else idx[0] | |
| cid = int(class_ids[i]) | |
| if 0 <= cid < len(COCO_CLASSES): | |
| label = COCO_CLASSES[cid] | |
| bx = cv_boxes[i] | |
| raw_items.append({ | |
| "base_label": label, | |
| "label": label, | |
| "confidence": round(float(confidences[i]) * 100, 1), | |
| "box": [max(0, bx[0]), max(0, bx[1]), min(w - 1, bx[0] + bx[2]), min(h - 1, bx[1] + bx[3])], | |
| "is_body_part": False | |
| }) | |
| except Exception: | |
| pass | |
| # --- 2. YOLOv8-Pose Body Parts & Keypoints Extraction --- | |
| if self.loaded_pose and self.pose_session: | |
| try: | |
| pose_out = self.pose_session.run(['output0'], {'images': blob_640})[0][0].T | |
| p_boxes = pose_out[:, :4] | |
| p_scores = pose_out[:, 4] | |
| p_kpts = pose_out[:, 5:].reshape(-1, 17, 3) | |
| p_mask = p_scores >= 0.35 | |
| p_boxes = p_boxes[p_mask] | |
| p_scores = p_scores[p_mask] | |
| p_kpts = p_kpts[p_mask] | |
| p_cv_boxes = [] | |
| for b in p_boxes: | |
| cx, cy, bw, bh = b | |
| p_cv_boxes.append([ | |
| int((cx - bw / 2.0) * x_factor), | |
| int((cy - bh / 2.0) * y_factor), | |
| int(bw * x_factor), | |
| int(bh * y_factor) | |
| ]) | |
| p_indices = cv2.dnn.NMSBoxes(p_cv_boxes, p_scores.tolist(), 0.35, 0.45) | |
| for idx in p_indices: | |
| i = idx if isinstance(idx, (int, np.integer)) else idx[0] | |
| kpts = p_kpts[i] | |
| kpts_scaled = [] | |
| for kx, ky, kc in kpts: | |
| kpts_scaled.append([int(kx * x_factor), int(ky * y_factor), float(kc)]) | |
| skeletons.append(kpts_scaled) | |
| # A. Head & Face (Nose:0, Eyes:1,2, Ears:3,4) | |
| head_pts = [kpts_scaled[j] for j in range(5) if kpts_scaled[j][2] > 0.30] | |
| if len(head_pts) >= 2: | |
| xs = [pt[0] for pt in head_pts] | |
| ys = [pt[1] for pt in head_pts] | |
| pad_x = max(25, int((max(xs) - min(xs)) * 0.7)) | |
| pad_y = max(30, int((max(ys) - min(ys)) * 1.0)) | |
| face_box = [ | |
| max(0, min(xs) - pad_x), | |
| max(0, min(ys) - pad_y), | |
| min(w - 1, max(xs) + pad_x), | |
| min(h - 1, max(ys) + pad_y // 2) | |
| ] | |
| conf = round(float(np.mean([pt[2] for pt in head_pts])) * 100, 1) | |
| raw_items.append({"base_label": "head / face", "label": "head / face", "confidence": conf, "box": face_box, "is_body_part": True}) | |
| # B. Left Hand / Wrist (Point 9) | |
| if kpts_scaled[9][2] > 0.35: | |
| x, y = kpts_scaled[9][:2] | |
| pad = 32 | |
| raw_items.append({ | |
| "base_label": "left hand", | |
| "label": "left hand", | |
| "confidence": round(kpts_scaled[9][2] * 100, 1), | |
| "box": [max(0, x - pad), max(0, y - pad), min(w - 1, x + pad), min(h - 1, y + pad)], | |
| "is_body_part": True | |
| }) | |
| # C. Right Hand / Wrist (Point 10) | |
| if kpts_scaled[10][2] > 0.35: | |
| x, y = kpts_scaled[10][:2] | |
| pad = 32 | |
| raw_items.append({ | |
| "base_label": "right hand", | |
| "label": "right hand", | |
| "confidence": round(kpts_scaled[10][2] * 100, 1), | |
| "box": [max(0, x - pad), max(0, y - pad), min(w - 1, x + pad), min(h - 1, y + pad)], | |
| "is_body_part": True | |
| }) | |
| # D. Chest / Upper Torso (Shoulders:5,6, Hips:11,12) | |
| torso_pts = [kpts_scaled[j] for j in [5, 6, 11, 12] if kpts_scaled[j][2] > 0.20] | |
| if len(torso_pts) >= 2: | |
| xs = [pt[0] for pt in torso_pts] | |
| ys = [pt[1] for pt in torso_pts] | |
| span_x = max(120, max(xs) - min(xs)) | |
| tx1 = max(0, min(xs) - int(span_x * 0.18)) | |
| tx2 = min(w - 1, max(xs) + int(span_x * 0.18)) | |
| ty1 = max(0, min(ys) - 20) | |
| ty2 = min(h - 1, max(max(ys) + 25, min(ys) + int(span_x * 0.85))) | |
| if ty2 - ty1 < 90: | |
| ty1 = max(0, ty2 - 110) | |
| raw_items.append({ | |
| "base_label": "chest / torso", | |
| "label": "chest / torso", | |
| "confidence": round(float(np.mean([pt[2] for pt in torso_pts])) * 100, 1), | |
| "box": [tx1, ty1, tx2, ty2], | |
| "is_body_part": True | |
| }) | |
| # E. Hips / Pelvic Region (Hips:11,12) | |
| hip_pts = [kpts_scaled[j] for j in [11, 12] if kpts_scaled[j][2] > 0.18] | |
| if len(hip_pts) >= 1: | |
| xs = [pt[0] for pt in hip_pts] | |
| ys = [pt[1] for pt in hip_pts] | |
| hx1, hy1 = max(0, min(xs) - 55), max(0, min(ys) - 35) | |
| hx2, hy2 = min(w - 1, max(xs) + 55), min(h - 1, max(ys) + 65) | |
| if hy2 - hy1 < 75: | |
| hy1 = max(0, hy2 - 85) | |
| raw_items.append({ | |
| "base_label": "pelvic / hips", | |
| "label": "pelvic / hips", | |
| "confidence": round(float(np.mean([pt[2] for pt in hip_pts])) * 100, 1), | |
| "box": [hx1, hy1, hx2, hy2], | |
| "is_body_part": True | |
| }) | |
| # F. Left & Right Arms (Shoulders/Elbows/Wrists) | |
| for arm_name, idxs in [("left arm", [5, 7, 9]), ("right arm", [6, 8, 10])]: | |
| apts = [kpts_scaled[j] for j in idxs if kpts_scaled[j][2] > 0.30] | |
| if len(apts) >= 2: | |
| xs, ys = [p[0] for p in apts], [p[1] for p in apts] | |
| raw_items.append({ | |
| "base_label": arm_name, | |
| "label": arm_name, | |
| "confidence": round(float(np.mean([p[2] for p in apts])) * 100, 1), | |
| "box": [max(0, min(xs) - 18), max(0, min(ys) - 18), min(w - 1, max(xs) + 18), min(h - 1, max(ys) + 18)], | |
| "is_body_part": True | |
| }) | |
| # G. Left & Right Legs (Hips/Knees/Ankles) | |
| for leg_name, idxs in [("left leg", [11, 13, 15]), ("right leg", [12, 14, 16])]: | |
| lpts = [kpts_scaled[j] for j in idxs if kpts_scaled[j][2] > 0.25] | |
| if len(lpts) >= 2: | |
| xs, ys = [p[0] for p in lpts], [p[1] for p in lpts] | |
| raw_items.append({ | |
| "base_label": leg_name, | |
| "label": leg_name, | |
| "confidence": round(float(np.mean([p[2] for p in lpts])) * 100, 1), | |
| "box": [max(0, min(xs) - 22), max(0, min(ys) - 18), min(w - 1, max(xs) + 22), min(h - 1, max(ys) + 18)], | |
| "is_body_part": True | |
| }) | |
| # H. Left & Right Feet / Ankles (Points 15, 16) | |
| for foot_name, pt_idx in [("left foot", 15), ("right foot", 16)]: | |
| if kpts_scaled[pt_idx][2] > 0.22: | |
| fx, fy = kpts_scaled[pt_idx][:2] | |
| fx1, fy1 = max(0, fx - 36), max(0, fy - 28) | |
| fx2, fy2 = min(w - 1, fx + 36), min(h - 1, fy + 38) | |
| raw_items.append({ | |
| "base_label": foot_name, | |
| "label": foot_name, | |
| "confidence": round(kpts_scaled[pt_idx][2] * 100, 1), | |
| "box": [fx1, fy1, fx2, fy2], | |
| "is_body_part": True | |
| }) | |
| except Exception: | |
| pass | |
| self._update_tracks(raw_items) | |
| self.latest_keypoints = skeletons | |
| formatted = [ | |
| { | |
| "id": t.track_id, | |
| "base_label": t.base_label, | |
| "label": t.label, | |
| "confidence": t.confidence, | |
| "box": list(t.box), | |
| "center": [ | |
| round((t.box[0] + t.box[2]) / (2.0 * w), 3), | |
| round((t.box[1] + t.box[3]) / (2.0 * h), 3) | |
| ] | |
| } | |
| for t in self.tracked_objects | |
| if t.missed_frames == 0 or t.hit_count >= 2 | |
| ] | |
| elapsed_ms = round((time.perf_counter() - t0) * 1000.0, 2) | |
| telemetry = { | |
| "gpu_active": self.gpu_active, | |
| "provider": self.active_provider, | |
| "keypoints_count": len(skeletons) * 17, | |
| "objects_count": len(formatted), | |
| "inference_ms": elapsed_ms | |
| } | |
| self.latest_detections = formatted | |
| self.latest_telemetry = telemetry | |
| self.last_detection_time = time.time() | |
| return formatted, telemetry | |
| def _update_tracks(self, raw_items: List[dict]): | |
| matched_track_idx = set() | |
| matched_raw_idx = set() | |
| for r_idx, r in enumerate(raw_items): | |
| r_base = r.get("base_label", r["label"]) | |
| best_iou = 0.30 | |
| best_t_idx = -1 | |
| for t_idx, track in enumerate(self.tracked_objects): | |
| if t_idx in matched_track_idx or track.base_label != r_base: | |
| continue | |
| iou = compute_box_iou(track.box, r["box"]) | |
| if iou > best_iou: | |
| best_iou = iou | |
| best_t_idx = t_idx | |
| if best_t_idx >= 0: | |
| self.tracked_objects[best_t_idx].update( | |
| r["label"], | |
| r["confidence"], | |
| r["box"] | |
| ) | |
| matched_track_idx.add(best_t_idx) | |
| matched_raw_idx.add(r_idx) | |
| for r_idx, r in enumerate(raw_items): | |
| if r_idx not in matched_raw_idx: | |
| track_id = f"obj_{self.next_track_num}" | |
| self.next_track_num += 1 | |
| self.tracked_objects.append( | |
| TrackedBox( | |
| track_id, | |
| r.get("base_label", r["label"]), | |
| r["label"], | |
| r["confidence"], | |
| r["box"] | |
| ) | |
| ) | |
| self.tracked_objects = [ | |
| t for i, t in enumerate(self.tracked_objects) | |
| if (i in matched_track_idx) or (t.missed_frames + 1 <= self.max_missed_frames) | |
| ] | |
| for i, t in enumerate(self.tracked_objects): | |
| if i not in matched_track_idx: | |
| t.missed_frames += 1 | |
| def render_overlay( | |
| self, | |
| frame: np.ndarray, | |
| detections: Optional[List[dict]] = None, | |
| telemetry: Optional[dict] = None, | |
| crop_meta: Optional[Tuple[int, int, int, int]] = None, | |
| active_target_id: Optional[str] = None | |
| ) -> np.ndarray: | |
| """Renders HUD overlays, skeletal wireframe, and targeted object highlight directly on frame.""" | |
| if frame is None: | |
| return frame | |
| out = frame.copy() | |
| h, w = out.shape[:2] | |
| dets = detections if detections is not None else self.latest_detections | |
| telem = telemetry if telemetry is not None else self.latest_telemetry | |
| # Crop transformation factors if rendered onto a zoomed crop | |
| if crop_meta is not None: | |
| cx1, cy1, cw, ch = crop_meta | |
| sx = w / float(cw) | |
| sy = h / float(ch) | |
| else: | |
| cx1, cy1, cw, ch = 0, 0, w, h | |
| sx, sy = 1.0, 1.0 | |
| # Draw Skeletal Lines & Keypoints | |
| for kpts in self.latest_keypoints: | |
| # Draw Bones | |
| for p1, p2 in SKELETON_PAIRS: | |
| x1, y1, c1 = kpts[p1] | |
| x2, y2, c2 = kpts[p2] | |
| if c1 > 0.35 and c2 > 0.35: | |
| mx1 = int((x1 - cx1) * sx) | |
| my1 = int((y1 - cy1) * sy) | |
| mx2 = int((x2 - cx1) * sx) | |
| my2 = int((y2 - cy1) * sy) | |
| bone_color = (0, 240, 255) if p1 < 5 else ((255, 200, 0) if p1 < 11 else (50, 255, 100)) | |
| cv2.line(out, (mx1, my1), (mx2, my2), bone_color, 2, cv2.LINE_AA) | |
| # Draw Joints | |
| for kx, ky, kc in kpts: | |
| if kc > 0.35: | |
| mx = int((kx - cx1) * sx) | |
| my = int((ky - cy1) * sy) | |
| if 0 <= mx < w and 0 <= my < h: | |
| cv2.circle(out, (mx, my), 4, (0, 255, 255), -1, cv2.LINE_AA) | |
| cv2.circle(out, (mx, my), 2, (20, 20, 20), -1, cv2.LINE_AA) | |
| # Draw Stabilized Object Bounding Boxes & Target Focus HUD | |
| for d in dets: | |
| label = d["label"] | |
| conf = d["confidence"] | |
| orig_box = d["box"] | |
| is_active_target = (active_target_id and d.get("id") == active_target_id) | |
| x1 = int((orig_box[0] - cx1) * sx) | |
| y1 = int((orig_box[1] - cy1) * sy) | |
| x2 = int((orig_box[2] - cx1) * sx) | |
| y2 = int((orig_box[3] - cy1) * sy) | |
| if x2 < 0 or x1 >= w or y2 < 0 or y1 >= h: | |
| continue | |
| x1 = max(0, min(w - 1, x1)) | |
| y1 = max(0, min(h - 1, y1)) | |
| x2 = max(0, min(w - 1, x2)) | |
| y2 = max(0, min(h - 1, y2)) | |
| base_lbl = d.get("base_label", label) | |
| if is_active_target: | |
| color = (0, 100, 255) | |
| else: | |
| color = COLOR_PALETTE.get(base_lbl, DEFAULT_COLOR) | |
| thick = 3 if is_active_target else 2 | |
| # Main Box & Corner Highlights | |
| cv2.rectangle(out, (x1, y1), (x2, y2), color, 1, lineType=cv2.LINE_AA) | |
| c_len = min(22, max(8, int((x2 - x1) * 0.2))) | |
| cv2.line(out, (x1, y1), (x1 + c_len, y1), color, thick) | |
| cv2.line(out, (x1, y1), (x1, y1 + c_len), color, thick) | |
| cv2.line(out, (x2, y1), (x2 - c_len, y1), color, thick) | |
| cv2.line(out, (x2, y1), (x2, y1 + c_len), color, thick) | |
| cv2.line(out, (x1, y2), (x1 + c_len, y2), color, thick) | |
| cv2.line(out, (x1, y2), (x1, y2 - c_len), color, thick) | |
| cv2.line(out, (x2, y2), (x2 - c_len, y2), color, thick) | |
| cv2.line(out, (x2, y2), (x2, y2 - c_len), color, thick) | |
| # Targeted Object Reticle | |
| if is_active_target: | |
| mcx = (x1 + x2) // 2 | |
| mcy = (y1 + y2) // 2 | |
| cv2.drawMarker(out, (mcx, mcy), (0, 100, 255), cv2.MARKER_CROSS, 20, 2) | |
| cv2.putText(out, "[LOCKED TARGET]", (x1, max(15, y1 - 22)), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (0, 100, 255), 1, cv2.LINE_AA) | |
| # Label Pill | |
| text = f"{label.upper()} {int(conf)}%" | |
| (tw, th), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.42, 1) | |
| ty = max(y1, th + 8) | |
| cv2.rectangle(out, (x1, ty - th - 5), (x1 + tw + 8, ty + 4), color, -1) | |
| cv2.putText(out, text, (x1 + 4, ty - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (10, 10, 10), 1, cv2.LINE_AA) | |
| # AI Tracker Telemetry HUD Pill (Top Right) | |
| is_gpu = telem.get("gpu_active", self.gpu_active) | |
| accel_tag = "T4 CUDA GPU" if is_gpu else "ONNX ENGINE" | |
| badge_text = f"AI TRACKER: {accel_tag} ({len(dets)} TARGETS)" | |
| badge_bg = (35, 120, 45) | |
| text_color = (220, 255, 220) | |
| (bw, bh), _ = cv2.getTextSize(badge_text, cv2.FONT_HERSHEY_SIMPLEX, 0.48, 1) | |
| badge_x = w - bw - 20 | |
| badge_y = 28 | |
| cv2.rectangle(out, (badge_x - 10, badge_y - bh - 6), (badge_x + bw + 10, badge_y + 6), (15, 15, 15), -1) | |
| cv2.rectangle(out, (badge_x - 8, badge_y - bh - 4), (badge_x + bw + 8, badge_y + 4), badge_bg, -1) | |
| cv2.putText(out, badge_text, (badge_x, badge_y), cv2.FONT_HERSHEY_SIMPLEX, 0.48, text_color, 1, cv2.LINE_AA) | |
| return out | |
| def detect_and_draw(self, frame: np.ndarray, draw_boxes: bool = True) -> Tuple[np.ndarray, List[dict]]: | |
| dets, telem = self.detect(frame) | |
| if draw_boxes: | |
| out = self.render_overlay(frame, dets, telem) | |
| return out, dets | |
| return frame, dets | |