When --max-feature-updates is enabled, feature stats are no longer discarded upon detecting a new face.

This commit is contained in:
Emiliana 2020-06-25 17:19:51 +02:00
parent d6cfa7bed3
commit d8d0b0bff0
2 changed files with 31 additions and 21 deletions

View file

@ -24,7 +24,7 @@ parser.add_argument("--faces", type=int, help="Set the maximum number of faces (
parser.add_argument("--scan-retinaface", type=int, help="When set to 1, scanning for additional faces will be performed using RetinaFace in a background thread, otherwise a simpler, faster face detection mechanism is used. When the maximum number of faces is 1, this option does nothing.", default=0)
parser.add_argument("--scan-every", type=int, help="Set after how many frames a scan for new faces should run", default=3)
parser.add_argument("--discard-after", type=int, help="Set the how long the tracker should keep looking for lost faces", default=10)
parser.add_argument("--max-feature-updates", type=int, help="When set to a value greater than zero, the internal minimum, maximum and median values of features are only updated this many times", default=0)
parser.add_argument("--max-feature-updates", type=int, help="This is the number of seconds after which feature min/max/medium values will no longer be updated once a face has been detected.", default=0)
parser.add_argument("--no-3d-adapt", type=int, help="When set to 1, the 3D face model will not be adapted to increase the fit", default=0)
parser.add_argument("--video-out", help="Set this to the filename of an AVI file to save the tracking visualization as a video", default=None)
parser.add_argument("--raw-rgb", type=int, help="When this is set, raw RGB frames of the size given with \"-W\" and \"-H\" are read from standard input instead of reading a video", default=0)

View file

@ -154,20 +154,25 @@ class Feature():
self.current_median = 0
self.update_count = 0
self.max_feature_updates = max_feature_updates
self.first_seen = -1
self.updating = True
def update(self, x):
def update(self, x, now=0):
if self.max_feature_updates > 0:
self.update_count += 1
new = self.update_state(x)
if self.first_seen == -1:
self.first_seen = now;
new = self.update_state(x, now=now)
filtered = self.last * self.alpha + new * (1 - self.alpha)
self.last = filtered
return filtered
def update_state(self, x):
updating = self.max_feature_updates < 1 or self.update_count < self.max_feature_updates
def update_state(self, x, now=0):
updating = self.updating and (self.max_feature_updates == 0 or now - self.first_seen < self.max_feature_updates)
if updating:
self.median + x
self.current_median = self.median.median()
else:
self.updating = False
median = self.current_median
if self.min is None:
@ -244,54 +249,55 @@ class FeatureExtractor():
def update(self, pts):
features = {}
now = time.perf_counter()
norm_distance_x = np.mean([pts[0, 0] - pts[16, 0], pts[1, 0] - pts[15, 0]])
norm_distance_y = np.mean([pts[27, 1] - pts[28, 1], pts[28, 1] - pts[29, 1], pts[29, 1] - pts[30, 1]])
a1, f_pts = self.align_points(pts[42], pts[45], pts[[43, 44, 47, 46]])
f = np.clip((np.mean([f_pts[0,1], f_pts[1,1]]) - np.mean([f_pts[2,1], f_pts[3,1]])) / norm_distance_y, 0, None)
features["eye_l"] = self.eye_l.update(f)
features["eye_l"] = self.eye_l.update(f, now)
a2, f_pts = self.align_points(pts[36], pts[39], pts[[37, 38, 41, 40]])
f = np.clip((np.mean([f_pts[0,1], f_pts[1,1]]) - np.mean([f_pts[2,1], f_pts[3,1]])) / norm_distance_y, 0, None)
features["eye_r"] = self.eye_r.update(f)
features["eye_r"] = self.eye_r.update(f, now)
a3, _ = self.align_points(pts[0], pts[16], [])
a4, _ = self.align_points(pts[31], pts[35], [])
norm_angle = np.mean(list(map(np.rad2deg, [a1, a2, a3, a4])))
a, f_pts = self.align_points(pts[22], pts[26], pts[[22, 23, 24, 25, 26]])
features["eyebrow_steepness_l"] = self.eyebrow_steepness_l.update(-np.rad2deg(a) - norm_angle)
features["eyebrow_steepness_l"] = self.eyebrow_steepness_l.update(-np.rad2deg(a) - norm_angle, now)
f = (np.mean([pts[22, 1], pts[26, 1]]) - pts[27, 1]) / norm_distance_y
features["eyebrow_updown_l"] = self.eyebrow_updown_l.update(f)
features["eyebrow_updown_l"] = self.eyebrow_updown_l.update(f, now)
f = np.max(np.abs(np.array(f_pts[1:4]) - f_pts[0, 1])) / norm_distance_y
features["eyebrow_quirk_l"] = self.eyebrow_quirk_l.update(f)
features["eyebrow_quirk_l"] = self.eyebrow_quirk_l.update(f, now)
a, f_pts = self.align_points(pts[17], pts[21], pts[[17, 18, 19, 20, 21]])
features["eyebrow_steepness_r"] = self.eyebrow_steepness_r.update(np.rad2deg(a) - norm_angle)
features["eyebrow_steepness_r"] = self.eyebrow_steepness_r.update(np.rad2deg(a) - norm_angle, now)
f = (np.mean([pts[17, 1], pts[21, 1]]) - pts[27, 1]) / norm_distance_y
features["eyebrow_updown_r"] = self.eyebrow_updown_r.update(f)
features["eyebrow_updown_r"] = self.eyebrow_updown_r.update(f, now)
f = np.max(np.abs(np.array(f_pts[1:4]) - f_pts[0, 1])) / norm_distance_y
features["eyebrow_quirk_r"] = self.eyebrow_quirk_r.update(f)
features["eyebrow_quirk_r"] = self.eyebrow_quirk_r.update(f, now)
upper_mouth_line = np.mean([pts[49, 1], pts[50, 1], pts[51, 1]])
center_line = np.mean([pts[50, 0], pts[60, 0], pts[27, 0], pts[30, 0], pts[64, 0], pts[55, 0]])
f = (upper_mouth_line - pts[62, 1]) / norm_distance_y
features["mouth_corner_updown_l"] = self.mouth_corner_updown_l.update(f)
features["mouth_corner_updown_l"] = self.mouth_corner_updown_l.update(f, now)
f = abs(center_line - pts[62, 0]) / norm_distance_x
features["mouth_corner_inout_l"] = self.mouth_corner_inout_l.update(f)
features["mouth_corner_inout_l"] = self.mouth_corner_inout_l.update(f, now)
f = (upper_mouth_line - pts[58, 1]) / norm_distance_y
features["mouth_corner_updown_r"] = self.mouth_corner_updown_r.update(f)
features["mouth_corner_updown_r"] = self.mouth_corner_updown_r.update(f, now)
f = abs(center_line - pts[58, 0]) / norm_distance_x
features["mouth_corner_inout_r"] = self.mouth_corner_inout_r.update(f)
features["mouth_corner_inout_r"] = self.mouth_corner_inout_r.update(f, now)
f = (np.mean([pts[59, 1], pts[60, 1], pts[61, 1]]) - np.mean([pts[65, 1], pts[64, 1], pts[63, 1]])) / norm_distance_y
features["mouth_open"] = self.mouth_open.update(f)
features["mouth_open"] = self.mouth_open.update(f, now)
f = abs(pts[58, 0] - pts[62, 0]) / norm_distance_x
features["mouth_wide"] = self.mouth_wide.update(f)
features["mouth_wide"] = self.mouth_wide.update(f, now)
return features
@ -312,6 +318,9 @@ class FaceInfo():
self.update_count_delta = 75.
self.update_count_max = 7500.
if self.tracker.max_feature_updates > 0:
self.features = FeatureExtractor(self.tracker.max_feature_updates)
def reset(self):
self.alive = False
self.conf = None
@ -327,7 +336,8 @@ class FaceInfo():
self.eye_blink = None
self.bbox = None
self.pnp_error = 0
self.features = FeatureExtractor(self.tracker.max_feature_updates)
if self.tracker.max_feature_updates < 1:
self.features = FeatureExtractor(0)
self.current_features = {}
self.contour = np.zeros((21,3))
self.update_counts = np.zeros((66,2))