Manual extractor: now you can specify face rectangle manually using ‘R Mouse button’.

It is useful for small, blurry, undetectable faces, animal faces.
https://i.imgur.com/8kmVgg8.jpg
Warning: such frames must be used only with XSeg workflow !
Landmarks cannot be placed on the face precisely, and they are actually used for positioning the red frame.
Try to keep the red frame the same as the adjacent frames.
This commit is contained in:
Colombo 2020-06-02 12:31:24 +04:00
parent e77865ce18
commit f91a604c6d

View file

@ -10,6 +10,7 @@ from pathlib import Path
import cv2 import cv2
import numpy as np import numpy as np
from numpy import linalg as npla
import facelib import facelib
from core import imagelib from core import imagelib
@ -94,7 +95,7 @@ class ExtractSubprocessor(Subprocessor):
self.cached_image = ( filepath, image ) self.cached_image = ( filepath, image )
h, w, c = image.shape h, w, c = image.shape
dflimg = DFLIMG.load (filepath) dflimg = DFLIMG.load (filepath)
extract_from_dflimg = (h == w and (dflimg is not None and dflimg.has_data()) ) extract_from_dflimg = (h == w and (dflimg is not None and dflimg.has_data()) )
@ -302,16 +303,16 @@ class ExtractSubprocessor(Subprocessor):
if not cpu_only: if not cpu_only:
if type == 'landmarks-manual': if type == 'landmarks-manual':
devices = [devices.get_best_device()] devices = [devices.get_best_device()]
result = [] result = []
for device in devices: for device in devices:
count = 1 count = 1
if count == 1: if count == 1:
result += [ (device.index, 'GPU', device.name, device.total_mem_gb) ] result += [ (device.index, 'GPU', device.name, device.total_mem_gb) ]
else: else:
for i in range(count): for i in range(count):
result += [ (device.index, 'GPU', f"{device.name} #{i}", device.total_mem_gb) ] result += [ (device.index, 'GPU', f"{device.name} #{i}", device.total_mem_gb) ]
return result return result
@ -358,6 +359,7 @@ class ExtractSubprocessor(Subprocessor):
self.cache_text_lines_img = (None, None) self.cache_text_lines_img = (None, None)
self.hide_help = False self.hide_help = False
self.landmarks_accurate = True self.landmarks_accurate = True
self.force_landmarks = False
self.landmarks = None self.landmarks = None
self.x = 0 self.x = 0
@ -397,7 +399,6 @@ class ExtractSubprocessor(Subprocessor):
def get_data(self, host_dict): def get_data(self, host_dict):
if self.type == 'landmarks-manual': if self.type == 'landmarks-manual':
need_remark_face = False need_remark_face = False
redraw_needed = False
while len (self.input_data) > 0: while len (self.input_data) > 0:
data = self.input_data[0] data = self.input_data[0]
filepath, data_rects, data_landmarks = data.filepath, data.rects, data.landmarks filepath, data_rects, data_landmarks = data.filepath, data.rects, data.landmarks
@ -410,11 +411,12 @@ class ExtractSubprocessor(Subprocessor):
self.landmarks = data_landmarks.pop() self.landmarks = data_landmarks.pop()
data_rects.clear() data_rects.clear()
data_landmarks.clear() data_landmarks.clear()
redraw_needed = True
self.rect_locked = True self.rect_locked = True
self.rect_size = ( self.rect[2] - self.rect[0] ) / 2 self.rect_size = ( self.rect[2] - self.rect[0] ) / 2
self.x = ( self.rect[0] + self.rect[2] ) / 2 self.x = ( self.rect[0] + self.rect[2] ) / 2
self.y = ( self.rect[1] + self.rect[3] ) / 2 self.y = ( self.rect[1] + self.rect[3] ) / 2
self.redraw()
if len(data_rects) == 0: if len(data_rects) == 0:
if self.cache_original_image[0] == filepath: if self.cache_original_image[0] == filepath:
@ -440,8 +442,8 @@ class ExtractSubprocessor(Subprocessor):
self.text_lines_img = self.cache_text_lines_img[1] self.text_lines_img = self.cache_text_lines_img[1]
else: else:
self.text_lines_img = (imagelib.get_draw_text_lines ( self.image, sh, self.text_lines_img = (imagelib.get_draw_text_lines ( self.image, sh,
[ '[Mouse click] - lock/unlock selection', [ '[L Mouse click] - lock/unlock selection. [Mouse wheel] - change rect',
'[Mouse wheel] - change rect', '[R Mouse Click] - manual face rectangle',
'[Enter] / [Space] - confirm / skip frame', '[Enter] / [Space] - confirm / skip frame',
'[,] [.]- prev frame, next frame. [Q] - skip remaining frames', '[,] [.]- prev frame, next frame. [Q] - skip remaining frames',
'[a] - accuracy on/off (more fps)', '[a] - accuracy on/off (more fps)',
@ -453,8 +455,10 @@ class ExtractSubprocessor(Subprocessor):
while True: while True:
io.process_messages(0.0001) io.process_messages(0.0001)
new_x = self.x if not self.force_landmarks:
new_y = self.y new_x = self.x
new_y = self.y
new_rect_size = self.rect_size new_rect_size = self.rect_size
mouse_events = io.get_mouse_events(self.wnd_name) mouse_events = io.get_mouse_events(self.wnd_name)
@ -465,8 +469,19 @@ class ExtractSubprocessor(Subprocessor):
diff = 1 if new_rect_size <= 40 else np.clip(new_rect_size / 10, 1, 10) diff = 1 if new_rect_size <= 40 else np.clip(new_rect_size / 10, 1, 10)
new_rect_size = max (5, new_rect_size + diff*mod) new_rect_size = max (5, new_rect_size + diff*mod)
elif ev == io.EVENT_LBUTTONDOWN: elif ev == io.EVENT_LBUTTONDOWN:
self.rect_locked = not self.rect_locked if self.force_landmarks:
self.extract_needed = True self.x = new_x
self.y = new_y
self.force_landmarks = False
self.rect_locked = True
self.redraw()
else:
self.rect_locked = not self.rect_locked
self.extract_needed = True
elif ev == io.EVENT_RBUTTONDOWN:
self.force_landmarks = not self.force_landmarks
if self.force_landmarks:
self.rect_locked = False
elif not self.rect_locked: elif not self.rect_locked:
new_x = np.clip (x, 0, w-1) / self.view_scale new_x = np.clip (x, 0, w-1) / self.view_scale
new_y = np.clip (y, 0, h-1) / self.view_scale new_y = np.clip (y, 0, h-1) / self.view_scale
@ -532,11 +547,35 @@ class ExtractSubprocessor(Subprocessor):
self.landmarks_accurate = not self.landmarks_accurate self.landmarks_accurate = not self.landmarks_accurate
break break
if self.x != new_x or \ if self.force_landmarks:
pt2 = np.float32([new_x, new_y])
pt1 = np.float32([self.x, self.y])
pt_vec_len = npla.norm(pt2-pt1)
pt_vec = pt2-pt1
if pt_vec_len != 0:
pt_vec /= pt_vec_len
self.rect_size = pt_vec_len
self.rect = ( int(self.x-self.rect_size),
int(self.y-self.rect_size),
int(self.x+self.rect_size),
int(self.y+self.rect_size) )
if pt_vec_len > 0:
lmrks = np.concatenate ( (np.zeros ((17,2), np.float32), LandmarksProcessor.landmarks_2D), axis=0 )
lmrks -= lmrks[30:31,:]
mat = cv2.getRotationMatrix2D( (0, 0), -np.arctan2( pt_vec[1], pt_vec[0] )*180/math.pi , pt_vec_len)
mat[:, 2] += (self.x, self.y)
self.landmarks = LandmarksProcessor.transform_points(lmrks, mat )
self.redraw()
elif self.x != new_x or \
self.y != new_y or \ self.y != new_y or \
self.rect_size != new_rect_size or \ self.rect_size != new_rect_size or \
self.extract_needed or \ self.extract_needed:
redraw_needed:
self.x = new_x self.x = new_x
self.y = new_y self.y = new_y
self.rect_size = new_rect_size self.rect_size = new_rect_size
@ -545,11 +584,7 @@ class ExtractSubprocessor(Subprocessor):
int(self.x+self.rect_size), int(self.x+self.rect_size),
int(self.y+self.rect_size) ) int(self.y+self.rect_size) )
if redraw_needed: return ExtractSubprocessor.Data (filepath, rects=[self.rect], landmarks_accurate=self.landmarks_accurate)
redraw_needed = False
return ExtractSubprocessor.Data (filepath, landmarks_accurate=self.landmarks_accurate)
else:
return ExtractSubprocessor.Data (filepath, rects=[self.rect], landmarks_accurate=self.landmarks_accurate)
else: else:
is_frame_done = True is_frame_done = True
@ -571,6 +606,40 @@ class ExtractSubprocessor(Subprocessor):
if not self.type != 'landmarks-manual': if not self.type != 'landmarks-manual':
self.input_data.insert(0, data) self.input_data.insert(0, data)
def redraw(self):
(h,w,c) = self.image.shape
if not self.hide_help:
image = cv2.addWeighted (self.image,1.0,self.text_lines_img,1.0,0)
else:
image = self.image.copy()
view_rect = (np.array(self.rect) * self.view_scale).astype(np.int).tolist()
view_landmarks = (np.array(self.landmarks) * self.view_scale).astype(np.int).tolist()
if self.rect_size <= 40:
scaled_rect_size = h // 3 if w > h else w // 3
p1 = (self.x - self.rect_size, self.y - self.rect_size)
p2 = (self.x + self.rect_size, self.y - self.rect_size)
p3 = (self.x - self.rect_size, self.y + self.rect_size)
wh = h if h < w else w
np1 = (w / 2 - wh / 4, h / 2 - wh / 4)
np2 = (w / 2 + wh / 4, h / 2 - wh / 4)
np3 = (w / 2 - wh / 4, h / 2 + wh / 4)
mat = cv2.getAffineTransform( np.float32([p1,p2,p3])*self.view_scale, np.float32([np1,np2,np3]) )
image = cv2.warpAffine(image, mat,(w,h) )
view_landmarks = LandmarksProcessor.transform_points (view_landmarks, mat)
landmarks_color = (255,255,0) if self.rect_locked else (0,255,0)
LandmarksProcessor.draw_rect_landmarks (image, view_rect, view_landmarks, self.face_type, self.image_size, landmarks_color=landmarks_color)
self.extract_needed = False
io.show_image (self.wnd_name, image)
#override #override
def on_result (self, host_dict, data, result): def on_result (self, host_dict, data, result):
if self.type == 'landmarks-manual': if self.type == 'landmarks-manual':
@ -579,37 +648,7 @@ class ExtractSubprocessor(Subprocessor):
if len(landmarks) != 0 and landmarks[0] is not None: if len(landmarks) != 0 and landmarks[0] is not None:
self.landmarks = landmarks[0] self.landmarks = landmarks[0]
(h,w,c) = self.image.shape self.redraw()
if not self.hide_help:
image = cv2.addWeighted (self.image,1.0,self.text_lines_img,1.0,0)
else:
image = self.image.copy()
view_rect = (np.array(self.rect) * self.view_scale).astype(np.int).tolist()
view_landmarks = (np.array(self.landmarks) * self.view_scale).astype(np.int).tolist()
if self.rect_size <= 40:
scaled_rect_size = h // 3 if w > h else w // 3
p1 = (self.x - self.rect_size, self.y - self.rect_size)
p2 = (self.x + self.rect_size, self.y - self.rect_size)
p3 = (self.x - self.rect_size, self.y + self.rect_size)
wh = h if h < w else w
np1 = (w / 2 - wh / 4, h / 2 - wh / 4)
np2 = (w / 2 + wh / 4, h / 2 - wh / 4)
np3 = (w / 2 - wh / 4, h / 2 + wh / 4)
mat = cv2.getAffineTransform( np.float32([p1,p2,p3])*self.view_scale, np.float32([np1,np2,np3]) )
image = cv2.warpAffine(image, mat,(w,h) )
view_landmarks = LandmarksProcessor.transform_points (view_landmarks, mat)
landmarks_color = (255,255,0) if self.rect_locked else (0,255,0)
LandmarksProcessor.draw_rect_landmarks (image, view_rect, view_landmarks, self.face_type, self.image_size, landmarks_color=landmarks_color)
self.extract_needed = False
io.show_image (self.wnd_name, image)
else: else:
self.result.append ( result ) self.result.append ( result )
io.progress_bar_inc(1) io.progress_bar_inc(1)
@ -690,14 +729,14 @@ def main(detector=None,
cpu_only = False, cpu_only = False,
force_gpu_idxs = None, force_gpu_idxs = None,
): ):
if not input_path.exists(): if not input_path.exists():
io.log_err ('Input directory not found. Please ensure it exists.') io.log_err ('Input directory not found. Please ensure it exists.')
return return
if face_type is not None: if face_type is not None:
face_type = FaceType.fromString(face_type) face_type = FaceType.fromString(face_type)
if face_type is None: if face_type is None:
if manual_output_debug_fix and output_path.exists(): if manual_output_debug_fix and output_path.exists():
files = pathex.get_image_paths(output_path) files = pathex.get_image_paths(output_path)
@ -705,14 +744,14 @@ def main(detector=None,
dflimg = DFLIMG.load(Path(files[0])) dflimg = DFLIMG.load(Path(files[0]))
if dflimg is not None and dflimg.has_data(): if dflimg is not None and dflimg.has_data():
face_type = FaceType.fromString ( dflimg.get_face_type() ) face_type = FaceType.fromString ( dflimg.get_face_type() )
if face_type is None: if face_type is None:
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() 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()
face_type = {'f' : FaceType.FULL, face_type = {'f' : FaceType.FULL,
'wf' : FaceType.WHOLE_FACE, 'wf' : FaceType.WHOLE_FACE,
'head' : FaceType.HEAD}[face_type] 'head' : FaceType.HEAD}[face_type]
image_size = 512 if face_type < FaceType.HEAD else 768 image_size = 512 if face_type < FaceType.HEAD else 768
if detector is None: if detector is None:
io.log_info ("Choose detector type.") io.log_info ("Choose detector type.")