GenMake-Crystal-Engine / face_detect.py
mhtbhatia's picture
Upload 12 files
cb789b0 verified
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