Skip to content

Commit a780439

Browse files
committed
Alignment improvements for #50.
Thanks @hbredin for the great feedback! 1. Make it clear dlib expects RGB images. 2. Keep the mean landmarks in the alignment source code rather than loading them from a txt file. 3. Normalize the mean landmarks between 0 and 1 for the transformation.
1 parent b5d7734 commit a780439

File tree

1 file changed

+58
-55
lines changed

1 file changed

+58
-55
lines changed

openface/alignment/naive_dlib.py

+58-55
Original file line numberDiff line numberDiff line change
@@ -25,85 +25,88 @@
2525
from .. import helper
2626
from .. import data
2727

28+
TEMPLATE = np.float32([
29+
(0.0792396913815, 0.339223741112), (0.0829219487236, 0.456955367943),
30+
(0.0967927109165, 0.575648016728), (0.122141515615, 0.691921601066),
31+
(0.168687863544, 0.800341263616), (0.239789390707, 0.895732504778),
32+
(0.325662452515, 0.977068762493), (0.422318282013, 1.04329000149),
33+
(0.531777802068, 1.06080371126), (0.641296298053, 1.03981924107),
34+
(0.738105872266, 0.972268833998), (0.824444363295, 0.889624082279),
35+
(0.894792677532, 0.792494155836), (0.939395486253, 0.681546643421),
36+
(0.96111933829, 0.562238253072), (0.970579841181, 0.441758925744),
37+
(0.971193274221, 0.322118743967), (0.163846223133, 0.249151738053),
38+
(0.21780354657, 0.204255863861), (0.291299351124, 0.192367318323),
39+
(0.367460241458, 0.203582210627), (0.4392945113, 0.233135599851),
40+
(0.586445962425, 0.228141644834), (0.660152671635, 0.195923841854),
41+
(0.737466449096, 0.182360984545), (0.813236546239, 0.192828009114),
42+
(0.8707571886, 0.235293377042), (0.51534533827, 0.31863546193),
43+
(0.516221448289, 0.396200446263), (0.517118861835, 0.473797687758),
44+
(0.51816430343, 0.553157797772), (0.433701156035, 0.604054457668),
45+
(0.475501237769, 0.62076344024), (0.520712933176, 0.634268222208),
46+
(0.565874114041, 0.618796581487), (0.607054002672, 0.60157671656),
47+
(0.252418718401, 0.331052263829), (0.298663015648, 0.302646354002),
48+
(0.355749724218, 0.303020650651), (0.403718978315, 0.33867711083),
49+
(0.352507175597, 0.349987615384), (0.296791759886, 0.350478978225),
50+
(0.631326076346, 0.334136672344), (0.679073381078, 0.29645404267),
51+
(0.73597236153, 0.294721285802), (0.782865376271, 0.321305281656),
52+
(0.740312274764, 0.341849376713), (0.68499850091, 0.343734332172),
53+
(0.353167761422, 0.746189164237), (0.414587777921, 0.719053835073),
54+
(0.477677654595, 0.706835892494), (0.522732900812, 0.717092275768),
55+
(0.569832064287, 0.705414478982), (0.635195811927, 0.71565572516),
56+
(0.69951672331, 0.739419187253), (0.639447159575, 0.805236879972),
57+
(0.576410514055, 0.835436670169), (0.525398405766, 0.841706377792),
58+
(0.47641545769, 0.837505914975), (0.41379548902, 0.810045601727),
59+
(0.380084785646, 0.749979603086), (0.477955996282, 0.74513234612),
60+
(0.523389793327, 0.748924302636), (0.571057789237, 0.74332894691),
61+
(0.672409137852, 0.744177032192), (0.572539621444, 0.776609286626),
62+
(0.5240106503, 0.783370783245), (0.477561227414, 0.778476346951)])
63+
64+
TPL_MIN, TPL_MAX = np.min(TEMPLATE, axis=0), np.max(TEMPLATE, axis=0)
65+
MINMAX_TEMPLATE = (TEMPLATE - TPL_MIN) / (TPL_MAX - TPL_MIN)
2866

2967
class NaiveDlib:
68+
OUTER_EYES_AND_BOTTOM_LIP = np.array([39, 42, 57])
69+
INNER_EYES_AND_NOSE = np.array([36, 45, 33])
3070

31-
def __init__(self, faceMean, facePredictor):
71+
def __init__(self, facePredictor):
3272
"""Initialize the dlib-based alignment."""
3373
self.detector = dlib.get_frontal_face_detector()
34-
self.normMeanLandmarks = loadMeanPoints(faceMean)
3574
self.predictor = dlib.shape_predictor(facePredictor)
3675

37-
def getAllFaceBoundingBoxes(self, img):
38-
return self.detector(img, 1)
76+
def getAllFaceBoundingBoxes(self, rgbImg):
77+
try:
78+
return self.detector(rgbImg, 1)
79+
except Exception as e:
80+
print("Warning: {}".format(e))
81+
# In rare cases, exceptions are thrown.
82+
return []
3983

40-
def getLargestFaceBoundingBox(self, img):
41-
faces = self.detector(img, 1)
84+
def getLargestFaceBoundingBox(self, rgbImg):
85+
faces = self.getAllFaceBoundingBoxes(rgbImg)
4286
if len(faces) > 0:
4387
return max(faces, key=lambda rect: rect.width() * rect.height())
4488

45-
def align(self, img, bb):
46-
points = self.predictor(img, bb)
89+
def align(self, rgbImg, bb):
90+
points = self.predictor(rgbImg, bb)
4791
return list(map(lambda p: (p.x, p.y), points.parts()))
4892

49-
EYES_AND_NOSE = np.array([36, 45, 33])
50-
def alignImg(self, method, size, img, bb=None,
51-
landmarks=None, landmarkIndices=EYES_AND_NOSE):
93+
def alignImg(self, method, size, rgbImg, bb=None,
94+
landmarks=None, landmarkIndices=INNER_EYES_AND_NOSE):
5295
if bb is None:
53-
try:
54-
bb = self.getLargestFaceBoundingBox(img)
55-
except Exception as e:
56-
print("Warning: {}".format(e))
57-
# In rare cases, exceptions are thrown.
58-
return
96+
bb = self.getLargestFaceBoundingBox(rgbImg)
5997
if bb is None:
60-
# Most failed detection attempts return here.
6198
return
6299

63100
if landmarks is None:
64-
landmarks = self.align(img, bb)
101+
landmarks = self.align(rgbImg, bb)
65102

66103
npLandmarks = np.float32(landmarks)
67-
npNormMeanLandmarks = np.float32(self.normMeanLandmarks)
68104

69105
if method == 'affine':
70106
H = cv2.getAffineTransform(npLandmarks[landmarkIndices],
71-
size*npNormMeanLandmarks[landmarkIndices])
72-
thumbnail = cv2.warpAffine(img, H, (size, size))
107+
size*MINMAX_TEMPLATE[landmarkIndices])
108+
thumbnail = cv2.warpAffine(rgbImg, H, (size, size))
73109
else:
74110
raise Exception('Unrecognized method: {}'.format(method))
75111

76112
return thumbnail
77-
78-
def transformPoints(points, bb, toImgCoords):
79-
if toImgCoords:
80-
def scale(p):
81-
(x, y) = p
82-
return (int((x * bb.width()) + bb.left()),
83-
int((y * bb.height()) + bb.top()))
84-
else:
85-
def scale(p):
86-
(x, y) = p
87-
return (float(x - bb.left()) / bb.width(),
88-
float(y - bb.top()) / bb.height())
89-
return list(map(scale, points))
90-
91-
92-
def loadMeanPoints(modelFname):
93-
def parse(line):
94-
(x, y) = line.strip().split(",")
95-
return (float(x), float(y))
96-
with open(modelFname, 'r') as f:
97-
return [parse(line) for line in f]
98-
99-
100-
def annotate(img, box, points=None, meanPoints=None):
101-
a = np.copy(img)
102-
bl = (box.left(), box.bottom())
103-
tr = (box.right(), box.top())
104-
cv2.rectangle(a, bl, tr, color=(153, 255, 204), thickness=3)
105-
for p in points:
106-
cv2.circle(a, center=p, radius=3, color=(102, 204, 255), thickness=-1)
107-
for p in meanPoints:
108-
cv2.circle(a, center=p, radius=3, color=(0, 0, 0), thickness=-1)
109-
return a

0 commit comments

Comments
 (0)