dreamlessx commited on
Commit
fd53eef
·
verified ·
1 Parent(s): 27cf3ae

Upload landmarkdiff/conditioning.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. landmarkdiff/conditioning.py +46 -14
landmarkdiff/conditioning.py CHANGED
@@ -1,7 +1,8 @@
1
- """Conditioning signal: static adjacency wireframe + auto-Canny.
2
 
3
- Static adjacency (not Delaunay) to avoid triangle inversion on big displacements.
4
- Auto-Canny adapts thresholds to skin tone (Fitzpatrick I-VI safe).
 
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, 289, 305, 460]
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
- while True:
 
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