import cv2 import numpy as np from PIL import Image, ImageDraw from huggingface_hub import hf_hub_download, login import os class SignatureDetector: def __init__(self, conf_threshold=0.2, iou_threshold=0.45): self.conf_threshold = conf_threshold self.iou_threshold = iou_threshold token = os.environ.get("HF_TOKEN") if token: login(token=token) print("📥 Downloading model from HuggingFace Hub...") self.model = self._load_model() print("✅ Model loaded successfully!") def _load_model(self): possible_files = ["model.onnx", "saved_model", "yolov8s.pt", "model.pt"] for filename in possible_files: try: model_path = hf_hub_download( repo_id="tech4humans/yolov8s-signature-detector", filename=filename ) print(f"✅ Found model: {filename}") return self._init_model(model_path, filename) except Exception: continue raise RuntimeError("❌ Tidak bisa menemukan file model di repo.") def _init_model(self, model_path, filename): if filename.endswith(".onnx"): import onnxruntime as ort session = ort.InferenceSession(model_path) return ("onnx", session) elif filename.endswith(".pt"): from ultralytics import YOLO return ("yolo", YOLO(model_path)) else: import tensorflow as tf model = tf.saved_model.load(model_path) return ("tf", model) def encode_image(self, image_path): image_data = np.fromfile(image_path, dtype="uint8") image_data = np.expand_dims(image_data, axis=0) return image_data def draw_result(self, image_path, result): image = Image.open(image_path) draw = ImageDraw.Draw(image) img_width, img_height = image.size for box, score in zip(result["detection_boxes"], result["detection_scores"]): if score >= self.conf_threshold: x1, y1, w, h = box x2, y2 = x1 + w, y1 + h x1 = int(x1 * img_width / 640) y1 = int(y1 * img_height / 640) x2 = int(x2 * img_width / 640) y2 = int(y2 * img_height / 640) color = tuple(np.random.randint(0, 256, size=3).tolist()) draw.rectangle([x1, y1, x2, y2], outline=color, width=3) draw.text((x1, y1 - 10), f"{score:.2f}", fill=color) return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) def _apply_nms(self, boxes, scores): if len(boxes) == 0: return [], [] boxes_xywh = [] for box in boxes: x1, y1, w, h = [float(v) for v in box] boxes_xywh.append([int(x1), int(y1), int(w), int(h)]) indices = cv2.dnn.NMSBoxes( bboxes=boxes_xywh, scores=[float(s) for s in scores], score_threshold=self.conf_threshold, nms_threshold=self.iou_threshold ) if len(indices) == 0: return [], [] indices = indices.flatten() filtered_boxes = [boxes[i] for i in indices] filtered_scores = [scores[i] for i in indices] return filtered_boxes, filtered_scores def detect_from_path(self, image_path): engine_type, model = self.model image_data = self.encode_image(image_path) if engine_type == "onnx": input_name = model.get_inputs()[0].name outputs = model.run(None, {input_name: image_data}) raw_boxes = outputs[0][0].tolist() raw_scores = outputs[1][0].tolist() filtered_boxes, filtered_scores = self._apply_nms(raw_boxes, raw_scores) result = { "detection_boxes": filtered_boxes, "detection_scores": filtered_scores } elif engine_type == "yolo": results = model( image_path, verbose=False, conf=self.conf_threshold, iou=self.iou_threshold ) boxes = results[0].boxes xyxyn = boxes.xyxyn.cpu().numpy() scores = boxes.conf.cpu().numpy() converted_boxes = [] for b in xyxyn: x1, y1, x2, y2 = b converted_boxes.append([x1*640, y1*640, (x2-x1)*640, (y2-y1)*640]) result = { "detection_boxes": converted_boxes, "detection_scores": scores } else: infer = model.signatures["serving_default"] outputs = infer(image_data) raw_boxes = outputs["detection_boxes"].numpy()[0].tolist() raw_scores = outputs["detection_scores"].numpy()[0].tolist() filtered_boxes, filtered_scores = self._apply_nms(raw_boxes, raw_scores) result = { "detection_boxes": filtered_boxes, "detection_scores": filtered_scores } num_signatures = sum(1 for s in result["detection_scores"] if s >= self.conf_threshold) annotated = self.draw_result(image_path, result) return annotated, num_signatures def detect(self, image_bgr, temp_path): cv2.imwrite(temp_path, image_bgr) return self.detect_from_path(temp_path)