Spaces:
Sleeping
Sleeping
Upload landmarkdiff/conditioning.py with huggingface_hub
Browse files- landmarkdiff/conditioning.py +46 -14
landmarkdiff/conditioning.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
-
"""Conditioning signal: static adjacency wireframe + auto-Canny.
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
|
@@ -35,7 +36,7 @@ RIGHT_EYEBROW = [300, 293, 334, 296, 336, 285, 295, 282, 283, 276]
|
|
| 35 |
|
| 36 |
NOSE_BRIDGE = [168, 6, 197, 195, 5, 4, 1]
|
| 37 |
NOSE_TIP = [94, 2, 326, 327, 294, 278, 279, 275, 274, 460, 456, 363, 370]
|
| 38 |
-
NOSE_BOTTOM = [19, 1, 274, 275, 440, 344, 278, 294, 460, 305, 289, 392
|
| 39 |
|
| 40 |
OUTER_LIPS = [
|
| 41 |
61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291,
|
|
@@ -47,12 +48,6 @@ INNER_LIPS = [
|
|
| 47 |
324, 318, 402, 317, 14, 87, 178, 88, 95, 78,
|
| 48 |
]
|
| 49 |
|
| 50 |
-
FACE_OVAL = [
|
| 51 |
-
10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288,
|
| 52 |
-
397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136,
|
| 53 |
-
172, 58, 132, 93, 234, 127, 162, 21, 54, 103, 67, 109, 10,
|
| 54 |
-
]
|
| 55 |
-
|
| 56 |
ALL_CONTOURS = [
|
| 57 |
JAWLINE_CONTOUR,
|
| 58 |
LEFT_EYE_CONTOUR,
|
|
@@ -61,6 +56,7 @@ ALL_CONTOURS = [
|
|
| 61 |
RIGHT_EYEBROW,
|
| 62 |
NOSE_BRIDGE,
|
| 63 |
NOSE_TIP,
|
|
|
|
| 64 |
OUTER_LIPS,
|
| 65 |
INNER_LIPS,
|
| 66 |
]
|
|
@@ -72,7 +68,17 @@ def render_wireframe(
|
|
| 72 |
height: int | None = None,
|
| 73 |
thickness: int = 1,
|
| 74 |
) -> np.ndarray:
|
| 75 |
-
"""Render static anatomical adjacency wireframe on black canvas.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
w = width or face.image_width
|
| 77 |
h = height or face.image_height
|
| 78 |
canvas = np.zeros((h, w), dtype=np.uint8)
|
|
@@ -92,7 +98,18 @@ def render_wireframe(
|
|
| 92 |
|
| 93 |
|
| 94 |
def auto_canny(image: np.ndarray) -> np.ndarray:
|
| 95 |
-
"""Auto-Canny edge detection with adaptive thresholds.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
median = np.median(image[image > 0]) if np.any(image > 0) else 128.0
|
| 97 |
low = int(max(0, 0.66 * median))
|
| 98 |
high = int(min(255, 1.33 * median))
|
|
@@ -105,7 +122,8 @@ def auto_canny(image: np.ndarray) -> np.ndarray:
|
|
| 105 |
element = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
|
| 106 |
temp = edges.copy()
|
| 107 |
|
| 108 |
-
|
|
|
|
| 109 |
eroded = cv2.erode(temp, element)
|
| 110 |
dilated = cv2.dilate(eroded, element)
|
| 111 |
diff = cv2.subtract(temp, dilated)
|
|
@@ -122,7 +140,21 @@ def generate_conditioning(
|
|
| 122 |
width: int | None = None,
|
| 123 |
height: int | None = None,
|
| 124 |
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 125 |
-
"""Generate full conditioning signal for ControlNet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
from landmarkdiff.landmarks import render_landmark_image
|
| 127 |
|
| 128 |
w = width or face.image_width
|
|
|
|
| 1 |
+
"""Conditioning signal generation: static adjacency wireframe + auto-Canny.
|
| 2 |
|
| 3 |
+
Uses a pre-defined anatomical adjacency matrix (NOT dynamic Delaunay) to prevent
|
| 4 |
+
triangle inversion on drastic landmark displacements. Auto-Canny adapts thresholds
|
| 5 |
+
to skin tone (Fitzpatrick I-VI safe).
|
| 6 |
"""
|
| 7 |
|
| 8 |
from __future__ import annotations
|
|
|
|
| 36 |
|
| 37 |
NOSE_BRIDGE = [168, 6, 197, 195, 5, 4, 1]
|
| 38 |
NOSE_TIP = [94, 2, 326, 327, 294, 278, 279, 275, 274, 460, 456, 363, 370]
|
| 39 |
+
NOSE_BOTTOM = [19, 1, 274, 275, 440, 344, 278, 294, 460, 305, 289, 392]
|
| 40 |
|
| 41 |
OUTER_LIPS = [
|
| 42 |
61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291,
|
|
|
|
| 48 |
324, 318, 402, 317, 14, 87, 178, 88, 95, 78,
|
| 49 |
]
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
ALL_CONTOURS = [
|
| 52 |
JAWLINE_CONTOUR,
|
| 53 |
LEFT_EYE_CONTOUR,
|
|
|
|
| 56 |
RIGHT_EYEBROW,
|
| 57 |
NOSE_BRIDGE,
|
| 58 |
NOSE_TIP,
|
| 59 |
+
NOSE_BOTTOM,
|
| 60 |
OUTER_LIPS,
|
| 61 |
INNER_LIPS,
|
| 62 |
]
|
|
|
|
| 68 |
height: int | None = None,
|
| 69 |
thickness: int = 1,
|
| 70 |
) -> np.ndarray:
|
| 71 |
+
"""Render static anatomical adjacency wireframe on black canvas.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
face: Facial landmarks (normalized coordinates).
|
| 75 |
+
width: Canvas width.
|
| 76 |
+
height: Canvas height.
|
| 77 |
+
thickness: Line thickness in pixels.
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
Grayscale wireframe image.
|
| 81 |
+
"""
|
| 82 |
w = width or face.image_width
|
| 83 |
h = height or face.image_height
|
| 84 |
canvas = np.zeros((h, w), dtype=np.uint8)
|
|
|
|
| 98 |
|
| 99 |
|
| 100 |
def auto_canny(image: np.ndarray) -> np.ndarray:
|
| 101 |
+
"""Auto-Canny edge detection with adaptive thresholds.
|
| 102 |
+
|
| 103 |
+
Uses median-based thresholds (0.66*median, 1.33*median) instead of
|
| 104 |
+
hardcoded 50/150 to handle all Fitzpatrick skin types.
|
| 105 |
+
Post-processes with morphological skeletonization for 1-pixel edges.
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
image: Grayscale input image.
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
Binary edge map (uint8, 0 or 255).
|
| 112 |
+
"""
|
| 113 |
median = np.median(image[image > 0]) if np.any(image > 0) else 128.0
|
| 114 |
low = int(max(0, 0.66 * median))
|
| 115 |
high = int(min(255, 1.33 * median))
|
|
|
|
| 122 |
element = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
|
| 123 |
temp = edges.copy()
|
| 124 |
|
| 125 |
+
max_iterations = max(edges.shape[0], edges.shape[1])
|
| 126 |
+
for _ in range(max_iterations):
|
| 127 |
eroded = cv2.erode(temp, element)
|
| 128 |
dilated = cv2.dilate(eroded, element)
|
| 129 |
diff = cv2.subtract(temp, dilated)
|
|
|
|
| 140 |
width: int | None = None,
|
| 141 |
height: int | None = None,
|
| 142 |
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 143 |
+
"""Generate full conditioning signal for ControlNet.
|
| 144 |
+
|
| 145 |
+
Returns three channels per the spec:
|
| 146 |
+
1. Rendered landmark dots (colored, BGR)
|
| 147 |
+
2. Canny edge map from static wireframe (grayscale)
|
| 148 |
+
3. Wireframe rendering (grayscale)
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
face: Extracted facial landmarks.
|
| 152 |
+
width: Output width.
|
| 153 |
+
height: Output height.
|
| 154 |
+
|
| 155 |
+
Returns:
|
| 156 |
+
Tuple of (landmark_image, canny_edges, wireframe).
|
| 157 |
+
"""
|
| 158 |
from landmarkdiff.landmarks import render_landmark_image
|
| 159 |
|
| 160 |
w = width or face.image_width
|