Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
iperov
GitHub Repository: iperov/deepfacelab
Path: blob/master/mainscripts/Extractor.py
628 views
1
import traceback
2
import math
3
import multiprocessing
4
import operator
5
import os
6
import shutil
7
import sys
8
import time
9
from pathlib import Path
10
11
import cv2
12
import numpy as np
13
from numpy import linalg as npla
14
15
import facelib
16
from core import imagelib
17
from core import mathlib
18
from facelib import FaceType, LandmarksProcessor
19
from core.interact import interact as io
20
from core.joblib import Subprocessor
21
from core.leras import nn
22
from core import pathex
23
from core.cv2ex import *
24
from DFLIMG import *
25
26
DEBUG = False
27
28
class ExtractSubprocessor(Subprocessor):
29
class Data(object):
30
def __init__(self, filepath=None, rects=None, landmarks = None, landmarks_accurate=True, manual=False, force_output_path=None, final_output_files = None):
31
self.filepath = filepath
32
self.rects = rects or []
33
self.rects_rotation = 0
34
self.landmarks_accurate = landmarks_accurate
35
self.manual = manual
36
self.landmarks = landmarks or []
37
self.force_output_path = force_output_path
38
self.final_output_files = final_output_files or []
39
self.faces_detected = 0
40
41
class Cli(Subprocessor.Cli):
42
43
#override
44
def on_initialize(self, client_dict):
45
self.type = client_dict['type']
46
self.image_size = client_dict['image_size']
47
self.jpeg_quality = client_dict['jpeg_quality']
48
self.face_type = client_dict['face_type']
49
self.max_faces_from_image = client_dict['max_faces_from_image']
50
self.device_idx = client_dict['device_idx']
51
self.cpu_only = client_dict['device_type'] == 'CPU'
52
self.final_output_path = client_dict['final_output_path']
53
self.output_debug_path = client_dict['output_debug_path']
54
55
#transfer and set stdin in order to work code.interact in debug subprocess
56
stdin_fd = client_dict['stdin_fd']
57
if stdin_fd is not None and DEBUG:
58
sys.stdin = os.fdopen(stdin_fd)
59
60
if self.cpu_only:
61
device_config = nn.DeviceConfig.CPU()
62
place_model_on_cpu = True
63
else:
64
device_config = nn.DeviceConfig.GPUIndexes ([self.device_idx])
65
place_model_on_cpu = device_config.devices[0].total_mem_gb < 4
66
67
if self.type == 'all' or 'rects' in self.type or 'landmarks' in self.type:
68
nn.initialize (device_config)
69
70
self.log_info (f"Running on {client_dict['device_name'] }")
71
72
if self.type == 'all' or self.type == 'rects-s3fd' or 'landmarks' in self.type:
73
self.rects_extractor = facelib.S3FDExtractor(place_model_on_cpu=place_model_on_cpu)
74
75
if self.type == 'all' or 'landmarks' in self.type:
76
# for head type, extract "3D landmarks"
77
self.landmarks_extractor = facelib.FANExtractor(landmarks_3D=self.face_type >= FaceType.HEAD,
78
place_model_on_cpu=place_model_on_cpu)
79
80
self.cached_image = (None, None)
81
82
#override
83
def process_data(self, data):
84
if 'landmarks' in self.type and len(data.rects) == 0:
85
return data
86
87
filepath = data.filepath
88
cached_filepath, image = self.cached_image
89
if cached_filepath != filepath:
90
image = cv2_imread( filepath )
91
if image is None:
92
self.log_err (f'Failed to open {filepath}, reason: cv2_imread() fail.')
93
return data
94
image = imagelib.normalize_channels(image, 3)
95
image = imagelib.cut_odd_image(image)
96
self.cached_image = ( filepath, image )
97
98
h, w, c = image.shape
99
100
if 'rects' in self.type or self.type == 'all':
101
data = ExtractSubprocessor.Cli.rects_stage (data=data,
102
image=image,
103
max_faces_from_image=self.max_faces_from_image,
104
rects_extractor=self.rects_extractor,
105
)
106
107
if 'landmarks' in self.type or self.type == 'all':
108
data = ExtractSubprocessor.Cli.landmarks_stage (data=data,
109
image=image,
110
landmarks_extractor=self.landmarks_extractor,
111
rects_extractor=self.rects_extractor,
112
)
113
114
if self.type == 'final' or self.type == 'all':
115
data = ExtractSubprocessor.Cli.final_stage(data=data,
116
image=image,
117
face_type=self.face_type,
118
image_size=self.image_size,
119
jpeg_quality=self.jpeg_quality,
120
output_debug_path=self.output_debug_path,
121
final_output_path=self.final_output_path,
122
)
123
return data
124
125
@staticmethod
126
def rects_stage(data,
127
image,
128
max_faces_from_image,
129
rects_extractor,
130
):
131
h,w,c = image.shape
132
if min(h,w) < 128:
133
# Image is too small
134
data.rects = []
135
else:
136
for rot in ([0, 90, 270, 180]):
137
if rot == 0:
138
rotated_image = image
139
elif rot == 90:
140
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
141
elif rot == 180:
142
rotated_image = image[::-1,::-1,:]
143
elif rot == 270:
144
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
145
rects = data.rects = rects_extractor.extract (rotated_image, is_bgr=True)
146
if len(rects) != 0:
147
data.rects_rotation = rot
148
break
149
if max_faces_from_image is not None and \
150
max_faces_from_image > 0 and \
151
len(data.rects) > 0:
152
data.rects = data.rects[0:max_faces_from_image]
153
return data
154
155
156
@staticmethod
157
def landmarks_stage(data,
158
image,
159
landmarks_extractor,
160
rects_extractor,
161
):
162
h, w, ch = image.shape
163
164
if data.rects_rotation == 0:
165
rotated_image = image
166
elif data.rects_rotation == 90:
167
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
168
elif data.rects_rotation == 180:
169
rotated_image = image[::-1,::-1,:]
170
elif data.rects_rotation == 270:
171
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
172
173
data.landmarks = landmarks_extractor.extract (rotated_image, data.rects, rects_extractor if (data.landmarks_accurate) else None, is_bgr=True)
174
if data.rects_rotation != 0:
175
for i, (rect, lmrks) in enumerate(zip(data.rects, data.landmarks)):
176
new_rect, new_lmrks = rect, lmrks
177
(l,t,r,b) = rect
178
if data.rects_rotation == 90:
179
new_rect = ( t, h-l, b, h-r)
180
if lmrks is not None:
181
new_lmrks = lmrks[:,::-1].copy()
182
new_lmrks[:,1] = h - new_lmrks[:,1]
183
elif data.rects_rotation == 180:
184
if lmrks is not None:
185
new_rect = ( w-l, h-t, w-r, h-b)
186
new_lmrks = lmrks.copy()
187
new_lmrks[:,0] = w - new_lmrks[:,0]
188
new_lmrks[:,1] = h - new_lmrks[:,1]
189
elif data.rects_rotation == 270:
190
new_rect = ( w-b, l, w-t, r )
191
if lmrks is not None:
192
new_lmrks = lmrks[:,::-1].copy()
193
new_lmrks[:,0] = w - new_lmrks[:,0]
194
data.rects[i], data.landmarks[i] = new_rect, new_lmrks
195
196
return data
197
198
@staticmethod
199
def final_stage(data,
200
image,
201
face_type,
202
image_size,
203
jpeg_quality,
204
output_debug_path=None,
205
final_output_path=None,
206
):
207
data.final_output_files = []
208
filepath = data.filepath
209
rects = data.rects
210
landmarks = data.landmarks
211
212
if output_debug_path is not None:
213
debug_image = image.copy()
214
215
face_idx = 0
216
for rect, image_landmarks in zip( rects, landmarks ):
217
if image_landmarks is None:
218
continue
219
220
rect = np.array(rect)
221
222
if face_type == FaceType.MARK_ONLY:
223
image_to_face_mat = None
224
face_image = image
225
face_image_landmarks = image_landmarks
226
else:
227
image_to_face_mat = LandmarksProcessor.get_transform_mat (image_landmarks, image_size, face_type)
228
229
face_image = cv2.warpAffine(image, image_to_face_mat, (image_size, image_size), cv2.INTER_LANCZOS4)
230
face_image_landmarks = LandmarksProcessor.transform_points (image_landmarks, image_to_face_mat)
231
232
landmarks_bbox = LandmarksProcessor.transform_points ( [ (0,0), (0,image_size-1), (image_size-1, image_size-1), (image_size-1,0) ], image_to_face_mat, True)
233
234
rect_area = mathlib.polygon_area(np.array(rect[[0,2,2,0]]).astype(np.float32), np.array(rect[[1,1,3,3]]).astype(np.float32))
235
landmarks_area = mathlib.polygon_area(landmarks_bbox[:,0].astype(np.float32), landmarks_bbox[:,1].astype(np.float32) )
236
237
if not data.manual and face_type <= FaceType.FULL_NO_ALIGN and landmarks_area > 4*rect_area: #get rid of faces which umeyama-landmark-area > 4*detector-rect-area
238
continue
239
240
if output_debug_path is not None:
241
LandmarksProcessor.draw_rect_landmarks (debug_image, rect, image_landmarks, face_type, image_size, transparent_mask=True)
242
243
output_path = final_output_path
244
if data.force_output_path is not None:
245
output_path = data.force_output_path
246
247
output_filepath = output_path / f"{filepath.stem}_{face_idx}.jpg"
248
cv2_imwrite(output_filepath, face_image, [int(cv2.IMWRITE_JPEG_QUALITY), jpeg_quality ] )
249
250
dflimg = DFLJPG.load(output_filepath)
251
dflimg.set_face_type(FaceType.toString(face_type))
252
dflimg.set_landmarks(face_image_landmarks.tolist())
253
dflimg.set_source_filename(filepath.name)
254
dflimg.set_source_rect(rect)
255
dflimg.set_source_landmarks(image_landmarks.tolist())
256
dflimg.set_image_to_face_mat(image_to_face_mat)
257
dflimg.save()
258
259
data.final_output_files.append (output_filepath)
260
face_idx += 1
261
data.faces_detected = face_idx
262
263
if output_debug_path is not None:
264
cv2_imwrite( output_debug_path / (filepath.stem+'.jpg'), debug_image, [int(cv2.IMWRITE_JPEG_QUALITY), 50] )
265
266
return data
267
268
#overridable
269
def get_data_name (self, data):
270
#return string identificator of your data
271
return data.filepath
272
273
@staticmethod
274
def get_devices_for_config (type, device_config):
275
devices = device_config.devices
276
cpu_only = len(devices) == 0
277
278
if 'rects' in type or \
279
'landmarks' in type or \
280
'all' in type:
281
282
if not cpu_only:
283
if type == 'landmarks-manual':
284
devices = [devices.get_best_device()]
285
286
result = []
287
288
for device in devices:
289
count = 1
290
291
if count == 1:
292
result += [ (device.index, 'GPU', device.name, device.total_mem_gb) ]
293
else:
294
for i in range(count):
295
result += [ (device.index, 'GPU', f"{device.name} #{i}", device.total_mem_gb) ]
296
297
return result
298
else:
299
if type == 'landmarks-manual':
300
return [ (0, 'CPU', 'CPU', 0 ) ]
301
else:
302
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in range( min(8, multiprocessing.cpu_count() // 2) ) ]
303
304
elif type == 'final':
305
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in (range(min(8, multiprocessing.cpu_count())) if not DEBUG else [0]) ]
306
307
def __init__(self, input_data, type, image_size=None, jpeg_quality=None, face_type=None, output_debug_path=None, manual_window_size=0, max_faces_from_image=0, final_output_path=None, device_config=None):
308
if type == 'landmarks-manual':
309
for x in input_data:
310
x.manual = True
311
312
self.input_data = input_data
313
314
self.type = type
315
self.image_size = image_size
316
self.jpeg_quality = jpeg_quality
317
self.face_type = face_type
318
self.output_debug_path = output_debug_path
319
self.final_output_path = final_output_path
320
self.manual_window_size = manual_window_size
321
self.max_faces_from_image = max_faces_from_image
322
self.result = []
323
324
self.devices = ExtractSubprocessor.get_devices_for_config(self.type, device_config)
325
326
super().__init__('Extractor', ExtractSubprocessor.Cli,
327
999999 if type == 'landmarks-manual' or DEBUG else 120)
328
329
#override
330
def on_clients_initialized(self):
331
if self.type == 'landmarks-manual':
332
self.wnd_name = 'Manual pass'
333
io.named_window(self.wnd_name)
334
io.capture_mouse(self.wnd_name)
335
io.capture_keys(self.wnd_name)
336
337
self.cache_original_image = (None, None)
338
self.cache_image = (None, None)
339
self.cache_text_lines_img = (None, None)
340
self.hide_help = False
341
self.landmarks_accurate = True
342
self.force_landmarks = False
343
344
self.landmarks = None
345
self.x = 0
346
self.y = 0
347
self.rect_size = 100
348
self.rect_locked = False
349
self.extract_needed = True
350
351
self.image = None
352
self.image_filepath = None
353
354
io.progress_bar (None, len (self.input_data))
355
356
#override
357
def on_clients_finalized(self):
358
if self.type == 'landmarks-manual':
359
io.destroy_all_windows()
360
361
io.progress_bar_close()
362
363
#override
364
def process_info_generator(self):
365
base_dict = {'type' : self.type,
366
'image_size': self.image_size,
367
'jpeg_quality' : self.jpeg_quality,
368
'face_type': self.face_type,
369
'max_faces_from_image':self.max_faces_from_image,
370
'output_debug_path': self.output_debug_path,
371
'final_output_path': self.final_output_path,
372
'stdin_fd': sys.stdin.fileno() }
373
374
375
for (device_idx, device_type, device_name, device_total_vram_gb) in self.devices:
376
client_dict = base_dict.copy()
377
client_dict['device_idx'] = device_idx
378
client_dict['device_name'] = device_name
379
client_dict['device_type'] = device_type
380
yield client_dict['device_name'], {}, client_dict
381
382
#override
383
def get_data(self, host_dict):
384
if self.type == 'landmarks-manual':
385
need_remark_face = False
386
while len (self.input_data) > 0:
387
data = self.input_data[0]
388
filepath, data_rects, data_landmarks = data.filepath, data.rects, data.landmarks
389
is_frame_done = False
390
391
if self.image_filepath != filepath:
392
self.image_filepath = filepath
393
if self.cache_original_image[0] == filepath:
394
self.original_image = self.cache_original_image[1]
395
else:
396
self.original_image = imagelib.normalize_channels( cv2_imread( filepath ), 3 )
397
398
self.cache_original_image = (filepath, self.original_image )
399
400
(h,w,c) = self.original_image.shape
401
self.view_scale = 1.0 if self.manual_window_size == 0 else self.manual_window_size / ( h * (16.0/9.0) )
402
403
if self.cache_image[0] == (h,w,c) + (self.view_scale,filepath):
404
self.image = self.cache_image[1]
405
else:
406
self.image = cv2.resize (self.original_image, ( int(w*self.view_scale), int(h*self.view_scale) ), interpolation=cv2.INTER_LINEAR)
407
self.cache_image = ( (h,w,c) + (self.view_scale,filepath), self.image )
408
409
(h,w,c) = self.image.shape
410
411
sh = (0,0, w, min(100, h) )
412
if self.cache_text_lines_img[0] == sh:
413
self.text_lines_img = self.cache_text_lines_img[1]
414
else:
415
self.text_lines_img = (imagelib.get_draw_text_lines ( self.image, sh,
416
[ '[L Mouse click] - lock/unlock selection. [Mouse wheel] - change rect',
417
'[R Mouse Click] - manual face rectangle',
418
'[Enter] / [Space] - confirm / skip frame',
419
'[,] [.]- prev frame, next frame. [Q] - skip remaining frames',
420
'[a] - accuracy on/off (more fps)',
421
'[h] - hide this help'
422
], (1, 1, 1) )*255).astype(np.uint8)
423
424
self.cache_text_lines_img = (sh, self.text_lines_img)
425
426
if need_remark_face: # need remark image from input data that already has a marked face?
427
need_remark_face = False
428
if len(data_rects) != 0: # If there was already a face then lock the rectangle to it until the mouse is clicked
429
self.rect = data_rects.pop()
430
self.landmarks = data_landmarks.pop()
431
data_rects.clear()
432
data_landmarks.clear()
433
434
self.rect_locked = True
435
self.rect_size = ( self.rect[2] - self.rect[0] ) / 2
436
self.x = ( self.rect[0] + self.rect[2] ) / 2
437
self.y = ( self.rect[1] + self.rect[3] ) / 2
438
self.redraw()
439
440
if len(data_rects) == 0:
441
(h,w,c) = self.image.shape
442
while True:
443
io.process_messages(0.0001)
444
445
if not self.force_landmarks:
446
new_x = self.x
447
new_y = self.y
448
449
new_rect_size = self.rect_size
450
451
mouse_events = io.get_mouse_events(self.wnd_name)
452
for ev in mouse_events:
453
(x, y, ev, flags) = ev
454
if ev == io.EVENT_MOUSEWHEEL and not self.rect_locked:
455
mod = 1 if flags > 0 else -1
456
diff = 1 if new_rect_size <= 40 else np.clip(new_rect_size / 10, 1, 10)
457
new_rect_size = max (5, new_rect_size + diff*mod)
458
elif ev == io.EVENT_LBUTTONDOWN:
459
if self.force_landmarks:
460
self.x = new_x
461
self.y = new_y
462
self.force_landmarks = False
463
self.rect_locked = True
464
self.redraw()
465
else:
466
self.rect_locked = not self.rect_locked
467
self.extract_needed = True
468
elif ev == io.EVENT_RBUTTONDOWN:
469
self.force_landmarks = not self.force_landmarks
470
if self.force_landmarks:
471
self.rect_locked = False
472
elif not self.rect_locked:
473
new_x = np.clip (x, 0, w-1) / self.view_scale
474
new_y = np.clip (y, 0, h-1) / self.view_scale
475
476
key_events = io.get_key_events(self.wnd_name)
477
key, chr_key, ctrl_pressed, alt_pressed, shift_pressed = key_events[-1] if len(key_events) > 0 else (0,0,False,False,False)
478
479
if key == ord('\r') or key == ord('\n'):
480
#confirm frame
481
is_frame_done = True
482
data_rects.append (self.rect)
483
data_landmarks.append (self.landmarks)
484
break
485
elif key == ord(' '):
486
#confirm skip frame
487
is_frame_done = True
488
break
489
elif key == ord(',') and len(self.result) > 0:
490
#go prev frame
491
492
if self.rect_locked:
493
self.rect_locked = False
494
# Only save the face if the rect is still locked
495
data_rects.append (self.rect)
496
data_landmarks.append (self.landmarks)
497
498
499
self.input_data.insert(0, self.result.pop() )
500
io.progress_bar_inc(-1)
501
need_remark_face = True
502
503
break
504
elif key == ord('.'):
505
#go next frame
506
507
if self.rect_locked:
508
self.rect_locked = False
509
# Only save the face if the rect is still locked
510
data_rects.append (self.rect)
511
data_landmarks.append (self.landmarks)
512
513
need_remark_face = True
514
is_frame_done = True
515
break
516
elif key == ord('q'):
517
#skip remaining
518
519
if self.rect_locked:
520
self.rect_locked = False
521
data_rects.append (self.rect)
522
data_landmarks.append (self.landmarks)
523
524
while len(self.input_data) > 0:
525
self.result.append( self.input_data.pop(0) )
526
io.progress_bar_inc(1)
527
528
break
529
530
elif key == ord('h'):
531
self.hide_help = not self.hide_help
532
break
533
elif key == ord('a'):
534
self.landmarks_accurate = not self.landmarks_accurate
535
break
536
537
if self.force_landmarks:
538
pt2 = np.float32([new_x, new_y])
539
pt1 = np.float32([self.x, self.y])
540
541
pt_vec_len = npla.norm(pt2-pt1)
542
pt_vec = pt2-pt1
543
if pt_vec_len != 0:
544
pt_vec /= pt_vec_len
545
546
self.rect_size = pt_vec_len
547
self.rect = ( int(self.x-self.rect_size),
548
int(self.y-self.rect_size),
549
int(self.x+self.rect_size),
550
int(self.y+self.rect_size) )
551
552
if pt_vec_len > 0:
553
lmrks = np.concatenate ( (np.zeros ((17,2), np.float32), LandmarksProcessor.landmarks_2D), axis=0 )
554
lmrks -= lmrks[30:31,:]
555
mat = cv2.getRotationMatrix2D( (0, 0), -np.arctan2( pt_vec[1], pt_vec[0] )*180/math.pi , pt_vec_len)
556
mat[:, 2] += (self.x, self.y)
557
self.landmarks = LandmarksProcessor.transform_points(lmrks, mat )
558
559
560
self.redraw()
561
562
elif self.x != new_x or \
563
self.y != new_y or \
564
self.rect_size != new_rect_size or \
565
self.extract_needed:
566
self.x = new_x
567
self.y = new_y
568
self.rect_size = new_rect_size
569
self.rect = ( int(self.x-self.rect_size),
570
int(self.y-self.rect_size),
571
int(self.x+self.rect_size),
572
int(self.y+self.rect_size) )
573
574
return ExtractSubprocessor.Data (filepath, rects=[self.rect], landmarks_accurate=self.landmarks_accurate)
575
576
else:
577
is_frame_done = True
578
579
if is_frame_done:
580
self.result.append ( data )
581
self.input_data.pop(0)
582
io.progress_bar_inc(1)
583
self.extract_needed = True
584
self.rect_locked = False
585
else:
586
if len (self.input_data) > 0:
587
return self.input_data.pop(0)
588
589
return None
590
591
#override
592
def on_data_return (self, host_dict, data):
593
if not self.type != 'landmarks-manual':
594
self.input_data.insert(0, data)
595
596
def redraw(self):
597
(h,w,c) = self.image.shape
598
599
if not self.hide_help:
600
image = cv2.addWeighted (self.image,1.0,self.text_lines_img,1.0,0)
601
else:
602
image = self.image.copy()
603
604
view_rect = (np.array(self.rect) * self.view_scale).astype(np.int).tolist()
605
view_landmarks = (np.array(self.landmarks) * self.view_scale).astype(np.int).tolist()
606
607
if self.rect_size <= 40:
608
scaled_rect_size = h // 3 if w > h else w // 3
609
610
p1 = (self.x - self.rect_size, self.y - self.rect_size)
611
p2 = (self.x + self.rect_size, self.y - self.rect_size)
612
p3 = (self.x - self.rect_size, self.y + self.rect_size)
613
614
wh = h if h < w else w
615
np1 = (w / 2 - wh / 4, h / 2 - wh / 4)
616
np2 = (w / 2 + wh / 4, h / 2 - wh / 4)
617
np3 = (w / 2 - wh / 4, h / 2 + wh / 4)
618
619
mat = cv2.getAffineTransform( np.float32([p1,p2,p3])*self.view_scale, np.float32([np1,np2,np3]) )
620
image = cv2.warpAffine(image, mat,(w,h) )
621
view_landmarks = LandmarksProcessor.transform_points (view_landmarks, mat)
622
623
landmarks_color = (255,255,0) if self.rect_locked else (0,255,0)
624
LandmarksProcessor.draw_rect_landmarks (image, view_rect, view_landmarks, self.face_type, self.image_size, landmarks_color=landmarks_color)
625
self.extract_needed = False
626
627
io.show_image (self.wnd_name, image)
628
629
630
#override
631
def on_result (self, host_dict, data, result):
632
if self.type == 'landmarks-manual':
633
filepath, landmarks = result.filepath, result.landmarks
634
635
if len(landmarks) != 0 and landmarks[0] is not None:
636
self.landmarks = landmarks[0]
637
638
self.redraw()
639
else:
640
self.result.append ( result )
641
io.progress_bar_inc(1)
642
643
644
645
#override
646
def get_result(self):
647
return self.result
648
649
650
class DeletedFilesSearcherSubprocessor(Subprocessor):
651
class Cli(Subprocessor.Cli):
652
#override
653
def on_initialize(self, client_dict):
654
self.debug_paths_stems = client_dict['debug_paths_stems']
655
return None
656
657
#override
658
def process_data(self, data):
659
input_path_stem = Path(data[0]).stem
660
return any ( [ input_path_stem == d_stem for d_stem in self.debug_paths_stems] )
661
662
#override
663
def get_data_name (self, data):
664
#return string identificator of your data
665
return data[0]
666
667
#override
668
def __init__(self, input_paths, debug_paths ):
669
self.input_paths = input_paths
670
self.debug_paths_stems = [ Path(d).stem for d in debug_paths]
671
self.result = []
672
super().__init__('DeletedFilesSearcherSubprocessor', DeletedFilesSearcherSubprocessor.Cli, 60)
673
674
#override
675
def process_info_generator(self):
676
for i in range(min(multiprocessing.cpu_count(), 8)):
677
yield 'CPU%d' % (i), {}, {'debug_paths_stems' : self.debug_paths_stems}
678
679
#override
680
def on_clients_initialized(self):
681
io.progress_bar ("Searching deleted files", len (self.input_paths))
682
683
#override
684
def on_clients_finalized(self):
685
io.progress_bar_close()
686
687
#override
688
def get_data(self, host_dict):
689
if len (self.input_paths) > 0:
690
return [self.input_paths.pop(0)]
691
return None
692
693
#override
694
def on_data_return (self, host_dict, data):
695
self.input_paths.insert(0, data[0])
696
697
#override
698
def on_result (self, host_dict, data, result):
699
if result == False:
700
self.result.append( data[0] )
701
io.progress_bar_inc(1)
702
703
#override
704
def get_result(self):
705
return self.result
706
707
def main(detector=None,
708
input_path=None,
709
output_path=None,
710
output_debug=None,
711
manual_fix=False,
712
manual_output_debug_fix=False,
713
manual_window_size=1368,
714
face_type='full_face',
715
max_faces_from_image=None,
716
image_size=None,
717
jpeg_quality=None,
718
cpu_only = False,
719
force_gpu_idxs = None,
720
):
721
722
if not input_path.exists():
723
io.log_err ('Input directory not found. Please ensure it exists.')
724
return
725
726
if not output_path.exists():
727
output_path.mkdir(parents=True, exist_ok=True)
728
729
if face_type is not None:
730
face_type = FaceType.fromString(face_type)
731
732
if face_type is None:
733
if manual_output_debug_fix:
734
files = pathex.get_image_paths(output_path)
735
if len(files) != 0:
736
dflimg = DFLIMG.load(Path(files[0]))
737
if dflimg is not None and dflimg.has_data():
738
face_type = FaceType.fromString ( dflimg.get_face_type() )
739
740
input_image_paths = pathex.get_image_unique_filestem_paths(input_path, verbose_print_func=io.log_info)
741
output_images_paths = pathex.get_image_paths(output_path)
742
output_debug_path = output_path.parent / (output_path.name + '_debug')
743
744
continue_extraction = False
745
if not manual_output_debug_fix and len(output_images_paths) > 0:
746
if len(output_images_paths) > 128:
747
continue_extraction = io.input_bool ("Continue extraction?", True, help_message="Extraction can be continued, but you must specify the same options again.")
748
749
if len(output_images_paths) > 128 and continue_extraction:
750
try:
751
input_image_paths = input_image_paths[ [ Path(x).stem for x in input_image_paths ].index ( Path(output_images_paths[-128]).stem.split('_')[0] ) : ]
752
except:
753
io.log_err("Error in fetching the last index. Extraction cannot be continued.")
754
return
755
elif input_path != output_path:
756
io.input(f"\n WARNING !!! \n {output_path} contains files! \n They will be deleted. \n Press enter to continue.\n")
757
for filename in output_images_paths:
758
Path(filename).unlink()
759
760
device_config = nn.DeviceConfig.GPUIndexes( force_gpu_idxs or nn.ask_choose_device_idxs(choose_only_one=detector=='manual', suggest_all_gpu=True) ) \
761
if not cpu_only else nn.DeviceConfig.CPU()
762
763
if face_type is None:
764
face_type = io.input_str ("Face type", 'wf', ['f','wf','head'], help_message="Full face / whole face / head. 'Whole face' covers full area of face include forehead. 'head' covers full head, but requires XSeg for src and dst faceset.").lower()
765
face_type = {'f' : FaceType.FULL,
766
'wf' : FaceType.WHOLE_FACE,
767
'head' : FaceType.HEAD}[face_type]
768
769
if max_faces_from_image is None:
770
max_faces_from_image = io.input_int(f"Max number of faces from image", 0, help_message="If you extract a src faceset that has frames with a large number of faces, it is advisable to set max faces to 3 to speed up extraction. 0 - unlimited")
771
772
if image_size is None:
773
image_size = io.input_int(f"Image size", 512 if face_type < FaceType.HEAD else 768, valid_range=[256,2048], help_message="Output image size. The higher image size, the worse face-enhancer works. Use higher than 512 value only if the source image is sharp enough and the face does not need to be enhanced.")
774
775
if jpeg_quality is None:
776
jpeg_quality = io.input_int(f"Jpeg quality", 90, valid_range=[1,100], help_message="Jpeg quality. The higher jpeg quality the larger the output file size.")
777
778
if detector is None:
779
io.log_info ("Choose detector type.")
780
io.log_info ("[0] S3FD")
781
io.log_info ("[1] manual")
782
detector = {0:'s3fd', 1:'manual'}[ io.input_int("", 0, [0,1]) ]
783
784
785
if output_debug is None:
786
output_debug = io.input_bool (f"Write debug images to {output_debug_path.name}?", False)
787
788
if output_debug:
789
output_debug_path.mkdir(parents=True, exist_ok=True)
790
791
if manual_output_debug_fix:
792
if not output_debug_path.exists():
793
io.log_err(f'{output_debug_path} not found. Re-extract faces with "Write debug images" option.')
794
return
795
else:
796
detector = 'manual'
797
io.log_info('Performing re-extract frames which were deleted from _debug directory.')
798
799
input_image_paths = DeletedFilesSearcherSubprocessor (input_image_paths, pathex.get_image_paths(output_debug_path) ).run()
800
input_image_paths = sorted (input_image_paths)
801
io.log_info('Found %d images.' % (len(input_image_paths)))
802
else:
803
if not continue_extraction and output_debug_path.exists():
804
for filename in pathex.get_image_paths(output_debug_path):
805
Path(filename).unlink()
806
807
images_found = len(input_image_paths)
808
faces_detected = 0
809
if images_found != 0:
810
if detector == 'manual':
811
io.log_info ('Performing manual extract...')
812
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(Path(filename)) for filename in input_image_paths ], 'landmarks-manual', image_size, jpeg_quality, face_type, output_debug_path if output_debug else None, manual_window_size=manual_window_size, device_config=device_config).run()
813
814
io.log_info ('Performing 3rd pass...')
815
data = ExtractSubprocessor (data, 'final', image_size, jpeg_quality, face_type, output_debug_path if output_debug else None, final_output_path=output_path, device_config=device_config).run()
816
817
else:
818
io.log_info ('Extracting faces...')
819
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(Path(filename)) for filename in input_image_paths ],
820
'all',
821
image_size,
822
jpeg_quality,
823
face_type,
824
output_debug_path if output_debug else None,
825
max_faces_from_image=max_faces_from_image,
826
final_output_path=output_path,
827
device_config=device_config).run()
828
829
faces_detected += sum([d.faces_detected for d in data])
830
831
if manual_fix:
832
if all ( np.array ( [ d.faces_detected > 0 for d in data] ) == True ):
833
io.log_info ('All faces are detected, manual fix not needed.')
834
else:
835
fix_data = [ ExtractSubprocessor.Data(d.filepath) for d in data if d.faces_detected == 0 ]
836
io.log_info ('Performing manual fix for %d images...' % (len(fix_data)) )
837
fix_data = ExtractSubprocessor (fix_data, 'landmarks-manual', image_size, jpeg_quality, face_type, output_debug_path if output_debug else None, manual_window_size=manual_window_size, device_config=device_config).run()
838
fix_data = ExtractSubprocessor (fix_data, 'final', image_size, jpeg_quality, face_type, output_debug_path if output_debug else None, final_output_path=output_path, device_config=device_config).run()
839
faces_detected += sum([d.faces_detected for d in fix_data])
840
841
842
io.log_info ('-------------------------')
843
io.log_info ('Images found: %d' % (images_found) )
844
io.log_info ('Faces detected: %d' % (faces_detected) )
845
io.log_info ('-------------------------')
846
847