mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(passport-photo): use bg-background for dropdown to match app theme
This commit is contained in:
@@ -570,7 +570,7 @@ export function PassportPhotoSettings() {
|
|||||||
|
|
||||||
{dropdownOpen && (
|
{dropdownOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed max-h-64 overflow-auto rounded-lg border border-border shadow-xl bg-white dark:bg-zinc-900"
|
className="fixed max-h-64 overflow-auto rounded-lg border border-border shadow-xl bg-background"
|
||||||
style={{
|
style={{
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
top: dropdownPos.top,
|
top: dropdownPos.top,
|
||||||
@@ -579,7 +579,7 @@ export function PassportPhotoSettings() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Search input */}
|
{/* Search input */}
|
||||||
<div className="sticky top-0 p-2 border-b border-border bg-white dark:bg-zinc-900">
|
<div className="sticky top-0 p-2 border-b border-border bg-background">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Face detection and blurring using MediaPipe."""
|
"""Face detection and blurring using MediaPipe."""
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def emit_progress(percent, stage):
|
def emit_progress(percent, stage):
|
||||||
@@ -8,6 +9,92 @@ def emit_progress(percent, stage):
|
|||||||
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Model path for new mp.tasks API ─────────────────────────────────
|
||||||
|
|
||||||
|
_FACE_DETECT_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.task"
|
||||||
|
_MODEL_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", ".models")
|
||||||
|
_FACE_DETECT_MODEL_PATH = os.path.join(_MODEL_DIR, "blaze_face_short_range.task")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_face_detect_model():
|
||||||
|
"""Download the face detector model if not present."""
|
||||||
|
if os.path.exists(_FACE_DETECT_MODEL_PATH):
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
os.makedirs(_MODEL_DIR, exist_ok=True)
|
||||||
|
import urllib.request
|
||||||
|
emit_progress(15, "Downloading face detection model")
|
||||||
|
urllib.request.urlretrieve(_FACE_DETECT_MODEL_URL, _FACE_DETECT_MODEL_PATH)
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_with_solutions(img_array, min_confidence):
|
||||||
|
"""Detect faces using legacy mp.solutions API (mediapipe < 0.10.30)."""
|
||||||
|
import mediapipe as mp
|
||||||
|
|
||||||
|
mp_face = mp.solutions.face_detection
|
||||||
|
results = None
|
||||||
|
for model_sel in [0, 1]:
|
||||||
|
detector = mp_face.FaceDetection(
|
||||||
|
model_selection=model_sel,
|
||||||
|
min_detection_confidence=min_confidence,
|
||||||
|
)
|
||||||
|
results = detector.process(img_array)
|
||||||
|
detector.close()
|
||||||
|
if results.detections:
|
||||||
|
break
|
||||||
|
|
||||||
|
detections = results.detections or []
|
||||||
|
if not detections:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ih, iw = img_array.shape[:2]
|
||||||
|
faces = []
|
||||||
|
for detection in detections:
|
||||||
|
bbox = detection.location_data.relative_bounding_box
|
||||||
|
faces.append({
|
||||||
|
"x": int(bbox.xmin * iw),
|
||||||
|
"y": int(bbox.ymin * ih),
|
||||||
|
"w": int(bbox.width * iw),
|
||||||
|
"h": int(bbox.height * ih),
|
||||||
|
})
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_with_tasks(img_array, min_confidence):
|
||||||
|
"""Detect faces using new mp.tasks API (mediapipe >= 0.10.30)."""
|
||||||
|
import mediapipe as mp
|
||||||
|
|
||||||
|
model_path = _ensure_face_detect_model()
|
||||||
|
options = mp.tasks.vision.FaceDetectorOptions(
|
||||||
|
base_options=mp.tasks.BaseOptions(model_asset_path=model_path),
|
||||||
|
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
||||||
|
min_detection_confidence=min_confidence,
|
||||||
|
)
|
||||||
|
detector = mp.tasks.vision.FaceDetector.create_from_options(options)
|
||||||
|
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_array)
|
||||||
|
result = detector.detect(mp_image)
|
||||||
|
detector.close()
|
||||||
|
|
||||||
|
faces = []
|
||||||
|
for detection in result.detections:
|
||||||
|
bbox = detection.bounding_box
|
||||||
|
faces.append({
|
||||||
|
"x": bbox.origin_x,
|
||||||
|
"y": bbox.origin_y,
|
||||||
|
"w": bbox.width,
|
||||||
|
"h": bbox.height,
|
||||||
|
})
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_faces(img_array, min_confidence):
|
||||||
|
"""Detect faces, trying legacy API first then falling back to tasks API."""
|
||||||
|
try:
|
||||||
|
return _detect_with_solutions(img_array, min_confidence)
|
||||||
|
except AttributeError:
|
||||||
|
return _detect_with_tasks(img_array, min_confidence)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
input_path = sys.argv[1]
|
input_path = sys.argv[1]
|
||||||
output_path = sys.argv[2]
|
output_path = sys.argv[2]
|
||||||
@@ -24,7 +111,6 @@ def main():
|
|||||||
img = Image.open(input_path).convert("RGB")
|
img = Image.open(input_path).convert("RGB")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import mediapipe as mp
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
emit_progress(20, "Ready")
|
emit_progress(20, "Ready")
|
||||||
@@ -34,56 +120,33 @@ def main():
|
|||||||
min_confidence = max(0.1, 1.0 - sensitivity)
|
min_confidence = max(0.1, 1.0 - sensitivity)
|
||||||
|
|
||||||
img_array = np.array(img)
|
img_array = np.array(img)
|
||||||
mp_face = mp.solutions.face_detection
|
|
||||||
|
|
||||||
# Try short-range model first (model_selection=0, best for faces
|
# Try legacy mp.solutions API first, fall back to mp.tasks
|
||||||
# within ~2m which covers most photos), then fall back to
|
|
||||||
# full-range model (model_selection=1) for distant/group shots.
|
|
||||||
emit_progress(25, "Scanning for faces")
|
emit_progress(25, "Scanning for faces")
|
||||||
results = None
|
faces = _detect_faces(img_array, min_confidence)
|
||||||
for model_sel in [0, 1]:
|
num_faces = len(faces)
|
||||||
detector = mp_face.FaceDetection(
|
|
||||||
model_selection=model_sel,
|
|
||||||
min_detection_confidence=min_confidence,
|
|
||||||
)
|
|
||||||
results = detector.process(img_array)
|
|
||||||
detector.close()
|
|
||||||
if results.detections:
|
|
||||||
break
|
|
||||||
|
|
||||||
faces = []
|
|
||||||
detections = results.detections or []
|
|
||||||
num_faces = len(detections)
|
|
||||||
emit_progress(50, f"Found {num_faces} face{'s' if num_faces != 1 else ''}")
|
emit_progress(50, f"Found {num_faces} face{'s' if num_faces != 1 else ''}")
|
||||||
|
|
||||||
if num_faces > 0:
|
if num_faces > 0 and not detect_only:
|
||||||
ih, iw = img_array.shape[:2]
|
for i, face in enumerate(faces):
|
||||||
for i, detection in enumerate(detections):
|
x, y, w, h = face["x"], face["y"], face["w"], face["h"]
|
||||||
bbox = detection.location_data.relative_bounding_box
|
|
||||||
x = int(bbox.xmin * iw)
|
|
||||||
y = int(bbox.ymin * ih)
|
|
||||||
w = int(bbox.width * iw)
|
|
||||||
h = int(bbox.height * ih)
|
|
||||||
|
|
||||||
if not detect_only:
|
# Add padding around the face
|
||||||
# Add padding around the face
|
pad = int(max(w, h) * 0.1)
|
||||||
pad = int(max(w, h) * 0.1)
|
x1 = max(0, x - pad)
|
||||||
x1 = max(0, x - pad)
|
y1 = max(0, y - pad)
|
||||||
y1 = max(0, y - pad)
|
x2 = min(img.width, x + w + pad)
|
||||||
x2 = min(img.width, x + w + pad)
|
y2 = min(img.height, y + h + pad)
|
||||||
y2 = min(img.height, y + h + pad)
|
|
||||||
|
|
||||||
face_region = img.crop((x1, y1, x2, y2))
|
face_region = img.crop((x1, y1, x2, y2))
|
||||||
blurred = face_region.filter(
|
blurred = face_region.filter(
|
||||||
ImageFilter.GaussianBlur(blur_radius)
|
ImageFilter.GaussianBlur(blur_radius)
|
||||||
)
|
)
|
||||||
img.paste(blurred, (x1, y1))
|
img.paste(blurred, (x1, y1))
|
||||||
emit_progress(
|
emit_progress(
|
||||||
50 + int((i + 1) / num_faces * 40),
|
50 + int((i + 1) / num_faces * 40),
|
||||||
f"Blurring face {i + 1} of {num_faces}",
|
f"Blurring face {i + 1} of {num_faces}",
|
||||||
)
|
)
|
||||||
|
|
||||||
faces.append({"x": x, "y": y, "w": w, "h": h})
|
|
||||||
|
|
||||||
if not detect_only:
|
if not detect_only:
|
||||||
emit_progress(95, "Saving result")
|
emit_progress(95, "Saving result")
|
||||||
|
|||||||
@@ -37,45 +37,90 @@ CODEFORMER_MODEL_PATH = os.environ.get(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Model path for new mp.tasks API ─────────────────────────────────
|
||||||
|
|
||||||
|
_FACE_DETECT_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.task"
|
||||||
|
_MODEL_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", ".models")
|
||||||
|
_FACE_DETECT_MODEL_PATH = os.path.join(_MODEL_DIR, "blaze_face_short_range.task")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_face_detect_model():
|
||||||
|
"""Download the face detector model if not present."""
|
||||||
|
if os.path.exists(_FACE_DETECT_MODEL_PATH):
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
os.makedirs(_MODEL_DIR, exist_ok=True)
|
||||||
|
import urllib.request
|
||||||
|
emit_progress(15, "Downloading face detection model")
|
||||||
|
urllib.request.urlretrieve(_FACE_DETECT_MODEL_URL, _FACE_DETECT_MODEL_PATH)
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
|
||||||
|
|
||||||
def detect_faces_mediapipe(img_array, sensitivity):
|
def detect_faces_mediapipe(img_array, sensitivity):
|
||||||
"""Detect faces using MediaPipe with dual-model approach.
|
"""Detect faces using MediaPipe with dual-model approach.
|
||||||
|
|
||||||
Returns a list of {x, y, w, h} dicts for each detected face.
|
Returns a list of {x, y, w, h} dicts for each detected face.
|
||||||
|
Tries legacy mp.solutions API first, falls back to mp.tasks.
|
||||||
"""
|
"""
|
||||||
import mediapipe as mp
|
import mediapipe as mp
|
||||||
|
|
||||||
min_confidence = max(0.1, 1.0 - sensitivity)
|
min_confidence = max(0.1, 1.0 - sensitivity)
|
||||||
mp_face = mp.solutions.face_detection
|
|
||||||
|
|
||||||
# Try short-range model first (model_selection=0, best for faces
|
try:
|
||||||
# within ~2m which covers most photos), then fall back to
|
mp_face = mp.solutions.face_detection
|
||||||
# full-range model (model_selection=1) for distant/group shots.
|
|
||||||
detections = []
|
# Try short-range model first (model_selection=0, best for faces
|
||||||
for model_sel in [0, 1]:
|
# within ~2m which covers most photos), then fall back to
|
||||||
detector = mp_face.FaceDetection(
|
# full-range model (model_selection=1) for distant/group shots.
|
||||||
model_selection=model_sel,
|
detections = []
|
||||||
|
for model_sel in [0, 1]:
|
||||||
|
detector = mp_face.FaceDetection(
|
||||||
|
model_selection=model_sel,
|
||||||
|
min_detection_confidence=min_confidence,
|
||||||
|
)
|
||||||
|
results = detector.process(img_array)
|
||||||
|
detector.close()
|
||||||
|
if results.detections:
|
||||||
|
detections = results.detections
|
||||||
|
break
|
||||||
|
|
||||||
|
if not detections:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ih, iw = img_array.shape[:2]
|
||||||
|
faces = []
|
||||||
|
for detection in detections:
|
||||||
|
bbox = detection.location_data.relative_bounding_box
|
||||||
|
faces.append({
|
||||||
|
"x": int(bbox.xmin * iw),
|
||||||
|
"y": int(bbox.ymin * ih),
|
||||||
|
"w": int(bbox.width * iw),
|
||||||
|
"h": int(bbox.height * ih),
|
||||||
|
})
|
||||||
|
return faces
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
# mediapipe >= 0.10.30 removed mp.solutions, use tasks API
|
||||||
|
model_path = _ensure_face_detect_model()
|
||||||
|
options = mp.tasks.vision.FaceDetectorOptions(
|
||||||
|
base_options=mp.tasks.BaseOptions(model_asset_path=model_path),
|
||||||
|
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
||||||
min_detection_confidence=min_confidence,
|
min_detection_confidence=min_confidence,
|
||||||
)
|
)
|
||||||
results = detector.process(img_array)
|
detector = mp.tasks.vision.FaceDetector.create_from_options(options)
|
||||||
|
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_array)
|
||||||
|
result = detector.detect(mp_image)
|
||||||
detector.close()
|
detector.close()
|
||||||
if results.detections:
|
|
||||||
detections = results.detections
|
|
||||||
break
|
|
||||||
|
|
||||||
if not detections:
|
faces = []
|
||||||
return []
|
for detection in result.detections:
|
||||||
|
bbox = detection.bounding_box
|
||||||
ih, iw = img_array.shape[:2]
|
faces.append({
|
||||||
faces = []
|
"x": bbox.origin_x,
|
||||||
for detection in detections:
|
"y": bbox.origin_y,
|
||||||
bbox = detection.location_data.relative_bounding_box
|
"w": bbox.width,
|
||||||
x = int(bbox.xmin * iw)
|
"h": bbox.height,
|
||||||
y = int(bbox.ymin * ih)
|
})
|
||||||
w = int(bbox.width * iw)
|
return faces
|
||||||
h = int(bbox.height * ih)
|
|
||||||
faces.append({"x": x, "y": y, "w": w, "h": h})
|
|
||||||
|
|
||||||
return faces
|
|
||||||
|
|
||||||
|
|
||||||
def enhance_with_gfpgan(img_array, only_center_face):
|
def enhance_with_gfpgan(img_array, only_center_face):
|
||||||
|
|||||||
@@ -9,6 +9,79 @@ def emit_progress(percent, stage):
|
|||||||
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Model path for new mp.tasks API ─────────────────────────────────
|
||||||
|
|
||||||
|
_FACE_MESH_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task"
|
||||||
|
_MODEL_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", ".models")
|
||||||
|
_FACE_MESH_MODEL_PATH = os.path.join(_MODEL_DIR, "face_landmarker.task")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_face_mesh_model():
|
||||||
|
"""Download the face landmarker model if not present."""
|
||||||
|
if os.path.exists(_FACE_MESH_MODEL_PATH):
|
||||||
|
return _FACE_MESH_MODEL_PATH
|
||||||
|
os.makedirs(_MODEL_DIR, exist_ok=True)
|
||||||
|
import urllib.request
|
||||||
|
emit_progress(15, "Downloading face mesh model")
|
||||||
|
urllib.request.urlretrieve(_FACE_MESH_MODEL_URL, _FACE_MESH_MODEL_PATH)
|
||||||
|
return _FACE_MESH_MODEL_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def _mesh_with_solutions(img_array, max_faces=10, min_confidence=0.5):
|
||||||
|
"""FaceMesh using legacy mp.solutions API (mediapipe < 0.10.30).
|
||||||
|
|
||||||
|
Returns list of landmark lists. Each landmark has .x, .y attributes.
|
||||||
|
"""
|
||||||
|
import mediapipe as mp
|
||||||
|
|
||||||
|
mesh = mp.solutions.face_mesh.FaceMesh(
|
||||||
|
static_image_mode=True,
|
||||||
|
max_num_faces=max_faces,
|
||||||
|
refine_landmarks=True,
|
||||||
|
min_detection_confidence=min_confidence,
|
||||||
|
)
|
||||||
|
results = mesh.process(img_array)
|
||||||
|
mesh.close()
|
||||||
|
|
||||||
|
if not results.multi_face_landmarks:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [face.landmark for face in results.multi_face_landmarks]
|
||||||
|
|
||||||
|
|
||||||
|
def _mesh_with_tasks(img_array, max_faces=10, min_confidence=0.5):
|
||||||
|
"""FaceMesh using new mp.tasks API (mediapipe >= 0.10.30).
|
||||||
|
|
||||||
|
Returns list of landmark lists. Each landmark has .x, .y attributes.
|
||||||
|
"""
|
||||||
|
import mediapipe as mp
|
||||||
|
|
||||||
|
model_path = _ensure_face_mesh_model()
|
||||||
|
options = mp.tasks.vision.FaceLandmarkerOptions(
|
||||||
|
base_options=mp.tasks.BaseOptions(model_asset_path=model_path),
|
||||||
|
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
||||||
|
num_faces=max_faces,
|
||||||
|
min_face_detection_confidence=min_confidence,
|
||||||
|
)
|
||||||
|
landmarker = mp.tasks.vision.FaceLandmarker.create_from_options(options)
|
||||||
|
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_array)
|
||||||
|
result = landmarker.detect(mp_image)
|
||||||
|
landmarker.close()
|
||||||
|
|
||||||
|
if not result.face_landmarks:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return result.face_landmarks
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_face_mesh(img_array, max_faces=10, min_confidence=0.5):
|
||||||
|
"""Detect face mesh, trying legacy API first then falling back to tasks API."""
|
||||||
|
try:
|
||||||
|
return _mesh_with_solutions(img_array, max_faces, min_confidence)
|
||||||
|
except AttributeError:
|
||||||
|
return _mesh_with_tasks(img_array, max_faces, min_confidence)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
input_path = sys.argv[1]
|
input_path = sys.argv[1]
|
||||||
output_path = sys.argv[2]
|
output_path = sys.argv[2]
|
||||||
@@ -65,28 +138,19 @@ def main():
|
|||||||
format_label = "jpg"
|
format_label = "jpg"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import mediapipe as mp
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
emit_progress(25, "Detecting faces")
|
emit_progress(25, "Detecting faces")
|
||||||
|
|
||||||
img_array = np.array(img)
|
img_array = np.array(img)
|
||||||
mesh = mp.solutions.face_mesh.FaceMesh(
|
|
||||||
static_image_mode=True,
|
|
||||||
max_num_faces=10,
|
|
||||||
refine_landmarks=True,
|
|
||||||
min_detection_confidence=0.5,
|
|
||||||
)
|
|
||||||
results = mesh.process(img_array)
|
|
||||||
mesh.close()
|
|
||||||
|
|
||||||
faces_detected = 0
|
# Try legacy mp.solutions API first, fall back to mp.tasks
|
||||||
|
all_face_landmarks = _detect_face_mesh(img_array)
|
||||||
|
|
||||||
|
faces_detected = len(all_face_landmarks)
|
||||||
eyes_corrected = 0
|
eyes_corrected = 0
|
||||||
|
|
||||||
if results.multi_face_landmarks:
|
|
||||||
faces_detected = len(results.multi_face_landmarks)
|
|
||||||
|
|
||||||
emit_progress(50, "Analyzing eyes")
|
emit_progress(50, "Analyzing eyes")
|
||||||
|
|
||||||
# Iris landmark indices
|
# Iris landmark indices
|
||||||
@@ -95,8 +159,7 @@ def main():
|
|||||||
|
|
||||||
if faces_detected > 0:
|
if faces_detected > 0:
|
||||||
all_eyes = []
|
all_eyes = []
|
||||||
for face_landmarks in results.multi_face_landmarks:
|
for landmarks in all_face_landmarks:
|
||||||
landmarks = face_landmarks.landmark
|
|
||||||
for iris_indices in [right_iris, left_iris]:
|
for iris_indices in [right_iris, left_iris]:
|
||||||
center_idx = iris_indices[0]
|
center_idx = iris_indices[0]
|
||||||
contour_indices = iris_indices[1:]
|
contour_indices = iris_indices[1:]
|
||||||
|
|||||||
@@ -217,6 +217,24 @@ def _get_codeformer_path():
|
|||||||
return CODEFORMER_LOCAL_PATH
|
return CODEFORMER_LOCAL_PATH
|
||||||
|
|
||||||
|
|
||||||
|
# ── Model path for new mp.tasks API ─────────────────────────────────
|
||||||
|
|
||||||
|
_FACE_DETECT_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.task"
|
||||||
|
_FACE_DETECT_MODEL_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", ".models")
|
||||||
|
_FACE_DETECT_MODEL_PATH = os.path.join(_FACE_DETECT_MODEL_DIR, "blaze_face_short_range.task")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_face_detect_model():
|
||||||
|
"""Download the face detector model if not present."""
|
||||||
|
if os.path.exists(_FACE_DETECT_MODEL_PATH):
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
os.makedirs(_FACE_DETECT_MODEL_DIR, exist_ok=True)
|
||||||
|
import urllib.request
|
||||||
|
emit_progress(15, "Downloading face detection model")
|
||||||
|
urllib.request.urlretrieve(_FACE_DETECT_MODEL_URL, _FACE_DETECT_MODEL_PATH)
|
||||||
|
return _FACE_DETECT_MODEL_PATH
|
||||||
|
|
||||||
|
|
||||||
def enhance_faces(img_bgr, fidelity=0.7):
|
def enhance_faces(img_bgr, fidelity=0.7):
|
||||||
"""Enhance faces in the image using CodeFormer ONNX.
|
"""Enhance faces in the image using CodeFormer ONNX.
|
||||||
|
|
||||||
@@ -239,20 +257,57 @@ def enhance_faces(img_bgr, fidelity=0.7):
|
|||||||
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
||||||
ih, iw = img_bgr.shape[:2]
|
ih, iw = img_bgr.shape[:2]
|
||||||
|
|
||||||
mp_face = mp.solutions.face_detection
|
try:
|
||||||
detections = []
|
mp_face = mp.solutions.face_detection
|
||||||
for model_sel in [0, 1]:
|
detections = []
|
||||||
detector = mp_face.FaceDetection(
|
for model_sel in [0, 1]:
|
||||||
model_selection=model_sel, min_detection_confidence=0.4
|
detector = mp_face.FaceDetection(
|
||||||
)
|
model_selection=model_sel, min_detection_confidence=0.4
|
||||||
results = detector.process(img_rgb)
|
)
|
||||||
detector.close()
|
results = detector.process(img_rgb)
|
||||||
if results.detections:
|
detector.close()
|
||||||
detections = results.detections
|
if results.detections:
|
||||||
break
|
detections = results.detections
|
||||||
|
break
|
||||||
|
|
||||||
if not detections:
|
if not detections:
|
||||||
return img_bgr, 0
|
return img_bgr, 0
|
||||||
|
|
||||||
|
face_boxes = []
|
||||||
|
for detection in detections:
|
||||||
|
bbox = detection.location_data.relative_bounding_box
|
||||||
|
face_boxes.append({
|
||||||
|
"x": int(bbox.xmin * iw),
|
||||||
|
"y": int(bbox.ymin * ih),
|
||||||
|
"w": int(bbox.width * iw),
|
||||||
|
"h": int(bbox.height * ih),
|
||||||
|
})
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
# mediapipe >= 0.10.30 removed mp.solutions, use tasks API
|
||||||
|
model_path = _ensure_face_detect_model()
|
||||||
|
options = mp.tasks.vision.FaceDetectorOptions(
|
||||||
|
base_options=mp.tasks.BaseOptions(model_asset_path=model_path),
|
||||||
|
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
||||||
|
min_detection_confidence=0.4,
|
||||||
|
)
|
||||||
|
fd = mp.tasks.vision.FaceDetector.create_from_options(options)
|
||||||
|
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_rgb)
|
||||||
|
result = fd.detect(mp_image)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
if not result.detections:
|
||||||
|
return img_bgr, 0
|
||||||
|
|
||||||
|
face_boxes = []
|
||||||
|
for detection in result.detections:
|
||||||
|
bbox = detection.bounding_box
|
||||||
|
face_boxes.append({
|
||||||
|
"x": bbox.origin_x,
|
||||||
|
"y": bbox.origin_y,
|
||||||
|
"w": bbox.width,
|
||||||
|
"h": bbox.height,
|
||||||
|
})
|
||||||
|
|
||||||
# Load CodeFormer model
|
# Load CodeFormer model
|
||||||
model_path = _get_codeformer_path()
|
model_path = _get_codeformer_path()
|
||||||
@@ -266,13 +321,11 @@ def enhance_faces(img_bgr, fidelity=0.7):
|
|||||||
result = img_bgr.copy()
|
result = img_bgr.copy()
|
||||||
faces_enhanced = 0
|
faces_enhanced = 0
|
||||||
|
|
||||||
for detection in detections:
|
for face_box in face_boxes:
|
||||||
bbox = detection.location_data.relative_bounding_box
|
x = face_box["x"]
|
||||||
# Convert relative coords to absolute
|
y = face_box["y"]
|
||||||
x = int(bbox.xmin * iw)
|
w = face_box["w"]
|
||||||
y = int(bbox.ymin * ih)
|
h = face_box["h"]
|
||||||
w = int(bbox.width * iw)
|
|
||||||
h = int(bbox.height * ih)
|
|
||||||
|
|
||||||
# Skip very small faces (under 48px) - enhancement won't help
|
# Skip very small faces (under 48px) - enhancement won't help
|
||||||
if w < 48 or h < 48:
|
if w < 48 or h < 48:
|
||||||
|
|||||||
Reference in New Issue
Block a user