Added files to enable using EmilianaVT's parameter calculations
Still need to hook them up and make a command line option for them
This commit is contained in:
parent
526c037336
commit
18b7c03cc6
5 changed files with 282 additions and 0 deletions
BIN
.emilianaFeatureExtractor.py.kate-swp
Normal file
BIN
.emilianaFeatureExtractor.py.kate-swp
Normal file
Binary file not shown.
19
Licenses/Remedian.txt
Normal file
19
Licenses/Remedian.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2016, Tim Menzies tim@menzies.us, MIT license v2.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
79
emilianaFeature.py
Normal file
79
emilianaFeature.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
from remedian import remedian
|
||||
|
||||
|
||||
|
||||
class Feature():
|
||||
def __init__(self, threshold=0.15, alpha=0.2, hard_factor=0.15, decay=0.001, max_feature_updates=0):
|
||||
self.median = remedian()
|
||||
self.min = None
|
||||
self.max = None
|
||||
self.hard_min = None
|
||||
self.hard_max = None
|
||||
self.threshold = threshold
|
||||
self.alpha = alpha
|
||||
self.hard_factor = hard_factor
|
||||
self.decay = decay
|
||||
self.last = 0
|
||||
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, now=0):
|
||||
if self.max_feature_updates > 0:
|
||||
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, 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:
|
||||
if x < median and (median - x) / median > self.threshold:
|
||||
if updating:
|
||||
self.min = x
|
||||
self.hard_min = self.min + self.hard_factor * (median - self.min)
|
||||
return -1
|
||||
return 0
|
||||
else:
|
||||
if x < self.min:
|
||||
if updating:
|
||||
self.min = x
|
||||
self.hard_min = self.min + self.hard_factor * (median - self.min)
|
||||
return -1
|
||||
if self.max is None:
|
||||
if x > median and (x - median) / median > self.threshold:
|
||||
if updating:
|
||||
self.max = x
|
||||
self.hard_max = self.max - self.hard_factor * (self.max - median)
|
||||
return 1
|
||||
return 0
|
||||
else:
|
||||
if x > self.max:
|
||||
if updating:
|
||||
self.max = x
|
||||
self.hard_max = self.max - self.hard_factor * (self.max - median)
|
||||
return 1
|
||||
|
||||
if updating:
|
||||
if self.min < self.hard_min:
|
||||
self.min = self.hard_min * self.decay + self.min * (1 - self.decay)
|
||||
if self.max > self.hard_max:
|
||||
self.max = self.hard_max * self.decay + self.max * (1 - self.decay)
|
||||
|
||||
if x < median:
|
||||
return - (1 - (x - self.min) / (median - self.min))
|
||||
elif x > median:
|
||||
return (x - median) / (self.max - median)
|
||||
|
||||
return 0
|
||||
102
emilianaFeatureExtractor.py
Normal file
102
emilianaFeatureExtractor.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
class FeatureExtractor():
|
||||
def __init__(self, max_feature_updates=0):
|
||||
self.eye_l = Feature(max_feature_updates=max_feature_updates)
|
||||
self.eye_r = Feature(max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_updown_l = Feature(max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_updown_r = Feature(max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_quirk_l = Feature(threshold=0.05, max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_quirk_r = Feature(threshold=0.05, max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_steepness_l = Feature(threshold=0.05, max_feature_updates=max_feature_updates)
|
||||
self.eyebrow_steepness_r = Feature(threshold=0.05, max_feature_updates=max_feature_updates)
|
||||
self.mouth_corner_updown_l = Feature(max_feature_updates=max_feature_updates)
|
||||
self.mouth_corner_updown_r = Feature(max_feature_updates=max_feature_updates)
|
||||
self.mouth_corner_inout_l = Feature(threshold=0.02, max_feature_updates=max_feature_updates)
|
||||
self.mouth_corner_inout_r = Feature(threshold=0.02, max_feature_updates=max_feature_updates)
|
||||
self.mouth_open = Feature(max_feature_updates=max_feature_updates)
|
||||
self.mouth_wide = Feature(threshold=0.02, max_feature_updates=max_feature_updates)
|
||||
|
||||
def align_points(self, a, b, pts):
|
||||
a = tuple(a)
|
||||
b = tuple(b)
|
||||
alpha = angle(a, b)
|
||||
alpha = np.rad2deg(alpha)
|
||||
if alpha >= 90:
|
||||
alpha = - (alpha - 180)
|
||||
if alpha <= -90:
|
||||
alpha = - (alpha + 180)
|
||||
alpha = np.deg2rad(alpha)
|
||||
aligned_pts = []
|
||||
for pt in pts:
|
||||
aligned_pts.append(np.array(rotate(a, pt, alpha)))
|
||||
return alpha, np.array(aligned_pts)
|
||||
|
||||
def update(self, pts, full=True):
|
||||
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 = abs((np.mean([f_pts[0,1], f_pts[1,1]]) - np.mean([f_pts[2,1], f_pts[3,1]])) / norm_distance_y)
|
||||
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 = abs((np.mean([f_pts[0,1], f_pts[1,1]]) - np.mean([f_pts[2,1], f_pts[3,1]])) / norm_distance_y)
|
||||
features["eye_r"] = self.eye_r.update(f, now)
|
||||
|
||||
if full:
|
||||
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, 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, 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, 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, now)
|
||||
else:
|
||||
features["eyebrow_steepness_l"] = 0.
|
||||
features["eyebrow_steepness_r"] = 0.
|
||||
features["eyebrow_quirk_l"] = 0.
|
||||
features["eyebrow_quirk_r"] = 0.
|
||||
|
||||
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, 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, 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, now)
|
||||
if full:
|
||||
f = abs(center_line - pts[62, 0]) / norm_distance_x
|
||||
features["mouth_corner_inout_l"] = self.mouth_corner_inout_l.update(f, now)
|
||||
else:
|
||||
features["mouth_corner_inout_l"] = 0.
|
||||
|
||||
f = (upper_mouth_line - pts[58, 1]) / norm_distance_y
|
||||
features["mouth_corner_updown_r"] = self.mouth_corner_updown_r.update(f, now)
|
||||
if full:
|
||||
f = abs(center_line - pts[58, 0]) / norm_distance_x
|
||||
features["mouth_corner_inout_r"] = self.mouth_corner_inout_r.update(f, now)
|
||||
else:
|
||||
features["mouth_corner_inout_r"] = 0.
|
||||
|
||||
f = abs(np.mean(pts[[59,60,61], 1], axis=0) - np.mean(pts[[63,64,65], 1], axis=0)) / norm_distance_y
|
||||
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, now)
|
||||
|
||||
return features
|
||||
82
remedian.py
Normal file
82
remedian.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
Watch over a stream of numbers, incrementally learning their median.
|
||||
|
||||
Implemented via nested lists. New numbers are added to `lst[i]` and
|
||||
when it fills up, it posts its median to `lst[i+1]`. Wen `lst[i+1]`
|
||||
fills up, it posts the medians of its medians to `lst[i+2]`. Etc.
|
||||
When a remedian is queried for the current median, it returns the
|
||||
median of the last list with any numbers.
|
||||
|
||||
This approach is quite space efficient . E.g. four nested lists,
|
||||
each with 64 items, require memory for 4*64 items yet can hold the
|
||||
median of the median of the median of the median of over 17 million
|
||||
numbers.
|
||||
|
||||
Example usage:
|
||||
|
||||
z=remedian()
|
||||
for i in range(1000):
|
||||
z + i
|
||||
if not i % 100:
|
||||
print(i, z.median())
|
||||
|
||||
Based on [The Remedian:A Robust Averaging Method for Large Data
|
||||
Sets](http://web.ipac.caltech.edu/staff/fmasci/home/astro_refs/Remedian.pdf).
|
||||
by Peter J. Rousseeuw and Gilbert W. Bassett Jr. Journal of the
|
||||
American Statistical Association March 1990, Vol. 85, No. 409,
|
||||
Theory and Methods
|
||||
|
||||
The code [remedianeg.py](remedianeg.py) compares this rig to just
|
||||
using Python's built-in sort then reporing the middle number.
|
||||
Assuming lists of length 64 and use of pypy3:
|
||||
|
||||
- Remedian is getting nearly as fast (within 20%) as raw sort after 500 items;
|
||||
- While at the same time, avoids having to store all the numbers in RAM;
|
||||
- Further, remedian's computed median is within 1% (or less) of the medians found via Python's sort.
|
||||
|
||||
_____
|
||||
|
||||
## Programmer's Guide
|
||||
|
||||
"""
|
||||
|
||||
# If `ordered` is `False`, do not sort `lst`
|
||||
def median(lst,ordered=False):
|
||||
assert lst,"median needs a non-empty list"
|
||||
n = len(lst)
|
||||
p = q = n//2
|
||||
if n < 3:
|
||||
p,q = 0, n-1
|
||||
else:
|
||||
lst = lst if ordered else sorted(lst)
|
||||
if not n % 2: # for even-length lists, use mean of mid 2 nums
|
||||
q = p -1
|
||||
return lst[p] if p==q else (lst[p]+lst[q])/2
|
||||
|
||||
class remedian:
|
||||
|
||||
# Initialization
|
||||
def __init__(i,inits=[], k=64, # after some experimentation, 64 works ok
|
||||
about = None):
|
||||
i.all,i.k = [],k
|
||||
i.more,i._median=None,None
|
||||
[i + x for x in inits]
|
||||
|
||||
# When full, push the median of current values to next list, then reset.
|
||||
def __add__(i,x):
|
||||
i._median = None
|
||||
i.all.append(x)
|
||||
if len(i.all) == i.k:
|
||||
i.more = i.more or remedian(k=i.k)
|
||||
i.more + i._medianPrim(i.all)
|
||||
i.all = [] # reset
|
||||
|
||||
# If there is a next list, ask its median. Else, work it out locally.
|
||||
def median(i):
|
||||
return i.more.median() if i.more else i._medianPrim(i.all)
|
||||
|
||||
# Only recompute median if we do not know it already.
|
||||
def _medianPrim(i,all):
|
||||
if i._median == None:
|
||||
i._median = median(all,ordered=False)
|
||||
return i._median
|
||||
Loading…
Add table
Add a link
Reference in a new issue