pyfaceau 1.0.3__cp310-cp310-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfaceau/__init__.py +19 -0
- pyfaceau/alignment/__init__.py +0 -0
- pyfaceau/alignment/calc_params.py +671 -0
- pyfaceau/alignment/face_aligner.py +352 -0
- pyfaceau/alignment/numba_calcparams_accelerator.py +244 -0
- pyfaceau/cython_histogram_median.cp310-win_amd64.pyd +0 -0
- pyfaceau/cython_rotation_update.cp310-win_amd64.pyd +0 -0
- pyfaceau/detectors/__init__.py +0 -0
- pyfaceau/detectors/pfld.py +128 -0
- pyfaceau/detectors/retinaface.py +352 -0
- pyfaceau/download_weights.py +134 -0
- pyfaceau/features/__init__.py +0 -0
- pyfaceau/features/histogram_median_tracker.py +335 -0
- pyfaceau/features/pdm.py +269 -0
- pyfaceau/features/triangulation.py +64 -0
- pyfaceau/parallel_pipeline.py +462 -0
- pyfaceau/pipeline.py +1083 -0
- pyfaceau/prediction/__init__.py +0 -0
- pyfaceau/prediction/au_predictor.py +434 -0
- pyfaceau/prediction/batched_au_predictor.py +269 -0
- pyfaceau/prediction/model_parser.py +337 -0
- pyfaceau/prediction/running_median.py +318 -0
- pyfaceau/prediction/running_median_fallback.py +200 -0
- pyfaceau/processor.py +270 -0
- pyfaceau/refinement/__init__.py +12 -0
- pyfaceau/refinement/svr_patch_expert.py +361 -0
- pyfaceau/refinement/targeted_refiner.py +362 -0
- pyfaceau/utils/__init__.py +0 -0
- pyfaceau/utils/cython_extensions/cython_histogram_median.c +35391 -0
- pyfaceau/utils/cython_extensions/cython_histogram_median.pyx +316 -0
- pyfaceau/utils/cython_extensions/cython_rotation_update.c +32262 -0
- pyfaceau/utils/cython_extensions/cython_rotation_update.pyx +211 -0
- pyfaceau/utils/cython_extensions/setup.py +47 -0
- pyfaceau-1.0.3.data/scripts/pyfaceau_gui.py +302 -0
- pyfaceau-1.0.3.dist-info/METADATA +466 -0
- pyfaceau-1.0.3.dist-info/RECORD +40 -0
- pyfaceau-1.0.3.dist-info/WHEEL +5 -0
- pyfaceau-1.0.3.dist-info/entry_points.txt +3 -0
- pyfaceau-1.0.3.dist-info/licenses/LICENSE +40 -0
- pyfaceau-1.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SVR Patch Expert implementation for CLNF landmark refinement
|
|
3
|
+
|
|
4
|
+
This module loads and uses SVR patch experts from OpenFace to refine landmark positions.
|
|
5
|
+
The implementation is based on OpenFace 2.2's SVR_patch_expert.cpp.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from typing import Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SVRPatchExpert:
|
|
13
|
+
"""
|
|
14
|
+
Single SVR patch expert for one landmark.
|
|
15
|
+
|
|
16
|
+
Similar to OpenFace's SVR_patch_expert class but in Python.
|
|
17
|
+
Each patch expert is a trained SVR model that evaluates local patches
|
|
18
|
+
around a landmark position to determine optimal placement.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.type = 0 # 0 = raw pixels, 1 = gradient features
|
|
23
|
+
self.confidence = 0.0
|
|
24
|
+
self.scaling = 1.0 # Logistic regression slope
|
|
25
|
+
self.bias = 0.0 # Logistic regression bias
|
|
26
|
+
self.weights = None # SVR weights matrix (similar to AU SVR!)
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
patch_type = "raw" if self.type == 0 else "gradient"
|
|
30
|
+
if self.weights is not None:
|
|
31
|
+
return f"SVRPatchExpert(type={patch_type}, weights_shape={self.weights.shape}, confidence={self.confidence:.3f})"
|
|
32
|
+
return f"SVRPatchExpert(type={patch_type}, not_loaded)"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SVRPatchExpertLoader:
|
|
36
|
+
"""
|
|
37
|
+
Loader for OpenFace SVR patch expert models.
|
|
38
|
+
|
|
39
|
+
Parses the svr_patches_*.txt format and loads patch experts for specified landmarks.
|
|
40
|
+
File format matches OpenFace 2.2's SVR patch expert format.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# OpenCV type constants (from OpenCV headers)
|
|
44
|
+
CV_8UC1 = 0
|
|
45
|
+
CV_32FC1 = 5
|
|
46
|
+
CV_64FC1 = 6
|
|
47
|
+
CV_32SC1 = 4
|
|
48
|
+
|
|
49
|
+
def __init__(self, filepath: str):
|
|
50
|
+
"""
|
|
51
|
+
Initialize loader with patch expert model file.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
filepath: Path to svr_patches_*.txt file
|
|
55
|
+
"""
|
|
56
|
+
self.filepath = filepath
|
|
57
|
+
self.scale = None
|
|
58
|
+
self.num_views = None
|
|
59
|
+
self.view_centers = []
|
|
60
|
+
self.visibility_indices = []
|
|
61
|
+
|
|
62
|
+
def load(self, target_landmarks: Optional[List[int]] = None) -> Dict[int, SVRPatchExpert]:
|
|
63
|
+
"""
|
|
64
|
+
Load patch experts from file.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
target_landmarks: List of landmark indices to load (0-67).
|
|
68
|
+
If None, loads all 68 landmarks.
|
|
69
|
+
Example: [17, 18, 19, 20, 21, 22, 26, 48, 54]
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Dictionary mapping landmark index -> SVRPatchExpert
|
|
73
|
+
"""
|
|
74
|
+
with open(self.filepath, 'r') as f:
|
|
75
|
+
# Parse header
|
|
76
|
+
self._parse_header(f)
|
|
77
|
+
|
|
78
|
+
# Load patch experts for each landmark
|
|
79
|
+
patch_experts = {}
|
|
80
|
+
for landmark_idx in range(68):
|
|
81
|
+
if target_landmarks is None or landmark_idx in target_landmarks:
|
|
82
|
+
expert = self._load_single_patch_expert(f, landmark_idx)
|
|
83
|
+
patch_experts[landmark_idx] = expert
|
|
84
|
+
else:
|
|
85
|
+
# Skip this patch expert to save memory
|
|
86
|
+
self._skip_patch_expert(f)
|
|
87
|
+
|
|
88
|
+
return patch_experts
|
|
89
|
+
|
|
90
|
+
def _parse_header(self, f):
|
|
91
|
+
"""Parse file header (scale, views, visibility)"""
|
|
92
|
+
# Skip comment line "# scaling factor of training"
|
|
93
|
+
f.readline()
|
|
94
|
+
|
|
95
|
+
# Read scaling factor
|
|
96
|
+
self.scale = float(f.readline().strip())
|
|
97
|
+
|
|
98
|
+
# Skip comment "# number of views"
|
|
99
|
+
f.readline()
|
|
100
|
+
|
|
101
|
+
# Read number of views
|
|
102
|
+
self.num_views = int(f.readline().strip())
|
|
103
|
+
|
|
104
|
+
# Skip comment "# centers of the views"
|
|
105
|
+
f.readline()
|
|
106
|
+
|
|
107
|
+
# Read view centers
|
|
108
|
+
# Each view has 6 lines: 3 metadata lines + 3 rotation values
|
|
109
|
+
for _ in range(self.num_views):
|
|
110
|
+
f.readline() # 3
|
|
111
|
+
f.readline() # 1
|
|
112
|
+
f.readline() # 6
|
|
113
|
+
f.readline() # rx value
|
|
114
|
+
f.readline() # ry value
|
|
115
|
+
f.readline() # rz value
|
|
116
|
+
|
|
117
|
+
# Skip visibility comment "# visibility indices per view"
|
|
118
|
+
f.readline()
|
|
119
|
+
|
|
120
|
+
# Read visibility indices (one block per view)
|
|
121
|
+
for _ in range(self.num_views):
|
|
122
|
+
# Each visibility block has:
|
|
123
|
+
# - num_landmarks line (68)
|
|
124
|
+
# - format line (1)
|
|
125
|
+
# - format line (4)
|
|
126
|
+
# - 68 visibility values (all on separate lines)
|
|
127
|
+
num_landmarks = int(f.readline().strip())
|
|
128
|
+
|
|
129
|
+
# Skip format lines
|
|
130
|
+
f.readline() # "1"
|
|
131
|
+
f.readline() # "4"
|
|
132
|
+
|
|
133
|
+
# Skip all visibility values
|
|
134
|
+
for _ in range(num_landmarks):
|
|
135
|
+
f.readline()
|
|
136
|
+
|
|
137
|
+
# Skip comment "# Patches themselves (1 line patches of a vertex)"
|
|
138
|
+
f.readline()
|
|
139
|
+
|
|
140
|
+
def _load_single_patch_expert(self, f, landmark_idx: int) -> SVRPatchExpert:
|
|
141
|
+
"""
|
|
142
|
+
Load a single patch expert from file.
|
|
143
|
+
|
|
144
|
+
Implements Multi_SVR_patch_expert::Read() and SVR_patch_expert::Read() from OpenFace.
|
|
145
|
+
All data is on a single line per view!
|
|
146
|
+
|
|
147
|
+
We only load view 0 (frontal) and skip the other 6 views.
|
|
148
|
+
"""
|
|
149
|
+
expert = SVRPatchExpert()
|
|
150
|
+
|
|
151
|
+
# For each view (we'll just use the first/frontal view for now)
|
|
152
|
+
for view_idx in range(self.num_views):
|
|
153
|
+
# Read entire patch expert line
|
|
154
|
+
line = f.readline().strip().split()
|
|
155
|
+
|
|
156
|
+
# Only process view 0 (frontal)
|
|
157
|
+
if view_idx != 0:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
idx = 0
|
|
161
|
+
read_type = int(line[idx])
|
|
162
|
+
idx += 1
|
|
163
|
+
|
|
164
|
+
# Handle Multi_SVR_patch_expert format (read_type=3)
|
|
165
|
+
if read_type == 3:
|
|
166
|
+
# Read Multi_SVR header
|
|
167
|
+
width = int(line[idx])
|
|
168
|
+
idx += 1
|
|
169
|
+
height = int(line[idx])
|
|
170
|
+
idx += 1
|
|
171
|
+
num_modalities_declared = int(line[idx])
|
|
172
|
+
idx += 1
|
|
173
|
+
|
|
174
|
+
# Note: num_modalities_declared may say 2, but we only have data for 1 modality on this line
|
|
175
|
+
# We'll just read the one SVR expert that's present
|
|
176
|
+
svr_read_type = int(line[idx])
|
|
177
|
+
idx += 1
|
|
178
|
+
|
|
179
|
+
if svr_read_type != 2:
|
|
180
|
+
raise ValueError(f"Expected SVR read_type=2, got {svr_read_type}")
|
|
181
|
+
|
|
182
|
+
# Read SVR patch expert data
|
|
183
|
+
expert.type = int(line[idx])
|
|
184
|
+
idx += 1
|
|
185
|
+
expert.confidence = float(line[idx])
|
|
186
|
+
idx += 1
|
|
187
|
+
expert.scaling = float(line[idx])
|
|
188
|
+
idx += 1
|
|
189
|
+
expert.bias = float(line[idx])
|
|
190
|
+
idx += 1
|
|
191
|
+
|
|
192
|
+
# Read matrix dimensions and type
|
|
193
|
+
rows = int(line[idx])
|
|
194
|
+
idx += 1
|
|
195
|
+
cols = int(line[idx])
|
|
196
|
+
idx += 1
|
|
197
|
+
mat_type = int(line[idx])
|
|
198
|
+
idx += 1
|
|
199
|
+
|
|
200
|
+
# Determine dtype
|
|
201
|
+
if mat_type == self.CV_32FC1:
|
|
202
|
+
dtype = np.float32
|
|
203
|
+
elif mat_type == self.CV_64FC1:
|
|
204
|
+
dtype = np.float64
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError(f"Unsupported matrix type: {mat_type}")
|
|
207
|
+
|
|
208
|
+
# Read matrix values
|
|
209
|
+
total_values = rows * cols
|
|
210
|
+
values = [dtype(line[idx + i]) for i in range(total_values)]
|
|
211
|
+
expert.weights = np.array(values, dtype=dtype).reshape(rows, cols)
|
|
212
|
+
# Transpose weights (OpenFace does this for Matlab compatibility)
|
|
213
|
+
expert.weights = expert.weights.T
|
|
214
|
+
|
|
215
|
+
elif read_type == 2:
|
|
216
|
+
# Simple SVR_patch_expert format (not wrapped in Multi_SVR)
|
|
217
|
+
# Read type, confidence, scaling, bias
|
|
218
|
+
expert.type = int(line[idx])
|
|
219
|
+
idx += 1
|
|
220
|
+
expert.confidence = float(line[idx])
|
|
221
|
+
idx += 1
|
|
222
|
+
expert.scaling = float(line[idx])
|
|
223
|
+
idx += 1
|
|
224
|
+
expert.bias = float(line[idx])
|
|
225
|
+
idx += 1
|
|
226
|
+
|
|
227
|
+
# Read matrix dimensions and type
|
|
228
|
+
rows = int(line[idx])
|
|
229
|
+
idx += 1
|
|
230
|
+
cols = int(line[idx])
|
|
231
|
+
idx += 1
|
|
232
|
+
mat_type = int(line[idx])
|
|
233
|
+
idx += 1
|
|
234
|
+
|
|
235
|
+
# Determine dtype
|
|
236
|
+
if mat_type == self.CV_32FC1:
|
|
237
|
+
dtype = np.float32
|
|
238
|
+
elif mat_type == self.CV_64FC1:
|
|
239
|
+
dtype = np.float64
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError(f"Unsupported matrix type: {mat_type}")
|
|
242
|
+
|
|
243
|
+
# Read matrix values
|
|
244
|
+
total_values = rows * cols
|
|
245
|
+
values = [dtype(line[idx + i]) for i in range(total_values)]
|
|
246
|
+
expert.weights = np.array(values, dtype=dtype).reshape(rows, cols)
|
|
247
|
+
|
|
248
|
+
# Transpose weights (OpenFace does this for Matlab compatibility)
|
|
249
|
+
expert.weights = expert.weights.T
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError(f"Unsupported read_type: {read_type}")
|
|
252
|
+
|
|
253
|
+
return expert
|
|
254
|
+
|
|
255
|
+
def _skip_patch_expert(self, f):
|
|
256
|
+
"""Skip a patch expert without loading (to save memory)"""
|
|
257
|
+
for view_idx in range(self.num_views):
|
|
258
|
+
# Each view is a single line, just skip it
|
|
259
|
+
f.readline()
|
|
260
|
+
|
|
261
|
+
def _read_mat(self, f) -> np.ndarray:
|
|
262
|
+
"""
|
|
263
|
+
Read matrix in OpenFace format.
|
|
264
|
+
|
|
265
|
+
Format:
|
|
266
|
+
rows cols type
|
|
267
|
+
value1 value2 value3 ...
|
|
268
|
+
|
|
269
|
+
This matches OpenFace's ReadMat() function.
|
|
270
|
+
"""
|
|
271
|
+
# Read dimensions and type
|
|
272
|
+
line = f.readline().strip().split()
|
|
273
|
+
rows = int(line[0])
|
|
274
|
+
cols = int(line[1])
|
|
275
|
+
mat_type = int(line[2])
|
|
276
|
+
|
|
277
|
+
# Determine numpy dtype from OpenCV type
|
|
278
|
+
if mat_type == self.CV_32FC1:
|
|
279
|
+
dtype = np.float32
|
|
280
|
+
elif mat_type == self.CV_64FC1:
|
|
281
|
+
dtype = np.float64
|
|
282
|
+
elif mat_type == self.CV_32SC1:
|
|
283
|
+
dtype = np.int32
|
|
284
|
+
elif mat_type == self.CV_8UC1:
|
|
285
|
+
dtype = np.uint8
|
|
286
|
+
else:
|
|
287
|
+
raise ValueError(f"Unsupported matrix type: {mat_type}")
|
|
288
|
+
|
|
289
|
+
# Read all values (may span multiple lines)
|
|
290
|
+
total_values = rows * cols
|
|
291
|
+
values = []
|
|
292
|
+
while len(values) < total_values:
|
|
293
|
+
line = f.readline().strip()
|
|
294
|
+
if line:
|
|
295
|
+
values.extend([dtype(v) for v in line.split()])
|
|
296
|
+
|
|
297
|
+
# Create matrix and reshape
|
|
298
|
+
mat = np.array(values, dtype=dtype).reshape(rows, cols)
|
|
299
|
+
return mat
|
|
300
|
+
|
|
301
|
+
def _skip_mat(self, f):
|
|
302
|
+
"""Skip matrix without reading values"""
|
|
303
|
+
# Read dimensions
|
|
304
|
+
line = f.readline().strip().split()
|
|
305
|
+
rows = int(line[0])
|
|
306
|
+
cols = int(line[1])
|
|
307
|
+
total_values = rows * cols
|
|
308
|
+
|
|
309
|
+
# Skip all values
|
|
310
|
+
values_read = 0
|
|
311
|
+
while values_read < total_values:
|
|
312
|
+
line = f.readline().strip()
|
|
313
|
+
if line:
|
|
314
|
+
values_read += len(line.split())
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_loader():
|
|
318
|
+
"""Quick test of the patch expert loader"""
|
|
319
|
+
import os
|
|
320
|
+
|
|
321
|
+
weights_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'weights')
|
|
322
|
+
patch_expert_file = os.path.join(weights_dir, 'svr_patches_0.25_general.txt')
|
|
323
|
+
|
|
324
|
+
if not os.path.exists(patch_expert_file):
|
|
325
|
+
print(f"Patch expert file not found: {patch_expert_file}")
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
print("Testing SVR Patch Expert Loader...")
|
|
329
|
+
print(f"Loading from: {patch_expert_file}")
|
|
330
|
+
|
|
331
|
+
# Load only critical landmarks
|
|
332
|
+
critical_landmarks = [17, 18, 19, 20, 21, 22, 26, 48, 54]
|
|
333
|
+
|
|
334
|
+
loader = SVRPatchExpertLoader(patch_expert_file)
|
|
335
|
+
experts = loader.load(target_landmarks=critical_landmarks)
|
|
336
|
+
|
|
337
|
+
print(f"\nLoaded {len(experts)} patch experts")
|
|
338
|
+
print(f"Scale: {loader.scale}")
|
|
339
|
+
print(f"Num views: {loader.num_views}")
|
|
340
|
+
|
|
341
|
+
print("\nPatch expert details:")
|
|
342
|
+
for idx, expert in experts.items():
|
|
343
|
+
print(f" Landmark {idx:2d}: {expert}")
|
|
344
|
+
|
|
345
|
+
# Test a specific expert
|
|
346
|
+
if 48 in experts:
|
|
347
|
+
expert = experts[48]
|
|
348
|
+
print(f"\nDetailed info for landmark 48 (left lip corner):")
|
|
349
|
+
print(f" Type: {'raw pixels' if expert.type == 0 else 'gradient'}")
|
|
350
|
+
print(f" Confidence: {expert.confidence:.4f}")
|
|
351
|
+
print(f" Scaling: {expert.scaling:.4f}")
|
|
352
|
+
print(f" Bias: {expert.bias:.4f}")
|
|
353
|
+
print(f" Weights shape: {expert.weights.shape}")
|
|
354
|
+
print(f" Weights dtype: {expert.weights.dtype}")
|
|
355
|
+
print(f" Weights range: [{expert.weights.min():.4f}, {expert.weights.max():.4f}]")
|
|
356
|
+
|
|
357
|
+
print("\nPatch expert loader test complete!")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
if __name__ == '__main__':
|
|
361
|
+
test_loader()
|