import os import cv2 import numpy as np # ========================================================= # GenMake Face Detection (Reliable) # - Tries OpenCV DNN model if present (best) # - Falls back to Haar if model not present (always works) # # Supported model formats: # 1) Caffe SSD: # - deploy.prototxt # - res10_300x300_ssd_iter_140000_fp16.caffemodel (or fp32) # # Put files in: # GenMakeEngineV1/models/face/ # ========================================================= def _models_dir() -> str: here = os.path.dirname(os.path.abspath(__file__)) return os.path.join(here, "models", "face") def _try_load_dnn(): """ Returns (net, kind) or (None, None) kind = 'caffe' """ mdir = _models_dir() # Caffe SSD (common, stable) prototxt = os.path.join(mdir, "deploy.prototxt") caffemodel = os.path.join(mdir, "res10_300x300_ssd_iter_140000_fp16.caffemodel") if os.path.exists(prototxt) and os.path.exists(caffemodel): net = cv2.dnn.readNetFromCaffe(prototxt, caffemodel) return net, "caffe" # Allow fp32 name too (optional) caffemodel2 = os.path.join(mdir, "res10_300x300_ssd_iter_140000.caffemodel") if os.path.exists(prototxt) and os.path.exists(caffemodel2): net = cv2.dnn.readNetFromCaffe(prototxt, caffemodel2) return net, "caffe" return None, None def _haar_detector(): path = os.path.join(cv2.data.haarcascades, "haarcascade_frontalface_default.xml") return cv2.CascadeClassifier(path) def _dnn_detect_faces(net, kind: str, bgr: np.ndarray, conf_thresh: float = 0.55): """ OpenCV DNN face detection. Returns list of (x1,y1,x2,y2). """ h, w = bgr.shape[:2] blob = cv2.dnn.blobFromImage( cv2.resize(bgr, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0), swapRB=False, crop=False ) net.setInput(blob) det = net.forward() boxes = [] # det shape: [1,1,N,7] => [batch, class, i, (img_id, label, conf, x1,y1,x2,y2)] for i in range(det.shape[2]): conf = float(det[0, 0, i, 2]) if conf < conf_thresh: continue x1 = int(det[0, 0, i, 3] * w) y1 = int(det[0, 0, i, 4] * h) x2 = int(det[0, 0, i, 5] * w) y2 = int(det[0, 0, i, 6] * h) # clamp 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)) # discard tiny if (x2 - x1) < 50 or (y2 - y1) < 50: continue boxes.append((x1, y1, x2, y2)) # largest first boxes.sort(key=lambda b: (b[2] - b[0]) * (b[3] - b[1]), reverse=True) return boxes def detect_faces(bgr: np.ndarray) -> list[tuple[int, int, int, int]]: """ Returns list of face boxes (x1,y1,x2,y2), largest first. Uses DNN if models exist, else Haar fallback. """ # 1) Try DNN net, kind = _try_load_dnn() if net is not None: try: return _dnn_detect_faces(net, kind, bgr, conf_thresh=0.55) except Exception: # If anything goes wrong, fall back safely pass # 2) Haar fallback (always works, less accurate) h, w = bgr.shape[:2] gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY) det = _haar_detector() faces = det.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(70, 70)) boxes = [] for (x, y, fw, fh) in faces: x1, y1 = max(0, x), max(0, y) x2, y2 = min(w, x + fw), min(h, y + fh) boxes.append((x1, y1, x2, y2)) boxes.sort(key=lambda b: (b[2] - b[0]) * (b[3] - b[1]), reverse=True) return boxes