align_dlib.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # Copyright 2015-2016 Carnegie Mellon University
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Module for dlib-based alignment."""
  15. # NOTE: This file has been copied from the openface project.
  16. # https://github.com/cmusatyalab/openface/blob/master/openface/align_dlib.py
  17. import cv2
  18. import dlib
  19. import numpy as np
  20. TEMPLATE = np.float32([
  21. (0.0792396913815, 0.339223741112), (0.0829219487236, 0.456955367943),
  22. (0.0967927109165, 0.575648016728), (0.122141515615, 0.691921601066),
  23. (0.168687863544, 0.800341263616), (0.239789390707, 0.895732504778),
  24. (0.325662452515, 0.977068762493), (0.422318282013, 1.04329000149),
  25. (0.531777802068, 1.06080371126), (0.641296298053, 1.03981924107),
  26. (0.738105872266, 0.972268833998), (0.824444363295, 0.889624082279),
  27. (0.894792677532, 0.792494155836), (0.939395486253, 0.681546643421),
  28. (0.96111933829, 0.562238253072), (0.970579841181, 0.441758925744),
  29. (0.971193274221, 0.322118743967), (0.163846223133, 0.249151738053),
  30. (0.21780354657, 0.204255863861), (0.291299351124, 0.192367318323),
  31. (0.367460241458, 0.203582210627), (0.4392945113, 0.233135599851),
  32. (0.586445962425, 0.228141644834), (0.660152671635, 0.195923841854),
  33. (0.737466449096, 0.182360984545), (0.813236546239, 0.192828009114),
  34. (0.8707571886, 0.235293377042), (0.51534533827, 0.31863546193),
  35. (0.516221448289, 0.396200446263), (0.517118861835, 0.473797687758),
  36. (0.51816430343, 0.553157797772), (0.433701156035, 0.604054457668),
  37. (0.475501237769, 0.62076344024), (0.520712933176, 0.634268222208),
  38. (0.565874114041, 0.618796581487), (0.607054002672, 0.60157671656),
  39. (0.252418718401, 0.331052263829), (0.298663015648, 0.302646354002),
  40. (0.355749724218, 0.303020650651), (0.403718978315, 0.33867711083),
  41. (0.352507175597, 0.349987615384), (0.296791759886, 0.350478978225),
  42. (0.631326076346, 0.334136672344), (0.679073381078, 0.29645404267),
  43. (0.73597236153, 0.294721285802), (0.782865376271, 0.321305281656),
  44. (0.740312274764, 0.341849376713), (0.68499850091, 0.343734332172),
  45. (0.353167761422, 0.746189164237), (0.414587777921, 0.719053835073),
  46. (0.477677654595, 0.706835892494), (0.522732900812, 0.717092275768),
  47. (0.569832064287, 0.705414478982), (0.635195811927, 0.71565572516),
  48. (0.69951672331, 0.739419187253), (0.639447159575, 0.805236879972),
  49. (0.576410514055, 0.835436670169), (0.525398405766, 0.841706377792),
  50. (0.47641545769, 0.837505914975), (0.41379548902, 0.810045601727),
  51. (0.380084785646, 0.749979603086), (0.477955996282, 0.74513234612),
  52. (0.523389793327, 0.748924302636), (0.571057789237, 0.74332894691),
  53. (0.672409137852, 0.744177032192), (0.572539621444, 0.776609286626),
  54. (0.5240106503, 0.783370783245), (0.477561227414, 0.778476346951)])
  55. INV_TEMPLATE = np.float32([
  56. (-0.04099179660567834, -0.008425234314031194, 2.575498465013183),
  57. (0.04062510634554352, -0.009678089746831375, -1.2534351452524177),
  58. (0.0003666902601348179, 0.01810332406086298, -0.32206331976076663)])
  59. TPL_MIN, TPL_MAX = np.min(TEMPLATE, axis=0), np.max(TEMPLATE, axis=0)
  60. MINMAX_TEMPLATE = (TEMPLATE - TPL_MIN) / (TPL_MAX - TPL_MIN)
  61. class AlignDlib:
  62. """
  63. Use `dlib's landmark estimation <http://blog.dlib.net/2014/08/real-time-face-pose-estimation.html>`_ to align faces.
  64. The alignment preprocess faces for input into a neural network.
  65. Faces are resized to the same size (such as 96x96) and transformed
  66. to make landmarks (such as the eyes and nose) appear at the same
  67. location on every image.
  68. Normalized landmarks:
  69. .. image:: ../images/dlib-landmark-mean.png
  70. """
  71. #: Landmark indices corresponding to the inner eyes and bottom lip.
  72. INNER_EYES_AND_BOTTOM_LIP = [39, 42, 57]
  73. #: Landmark indices corresponding to the outer eyes and nose.
  74. OUTER_EYES_AND_NOSE = [36, 45, 33]
  75. def __init__(self, facePredictor):
  76. """
  77. Instantiate an 'AlignDlib' object.
  78. :param facePredictor: The path to dlib's
  79. :type facePredictor: str
  80. """
  81. assert facePredictor is not None
  82. #pylint: disable=no-member
  83. self.detector = dlib.get_frontal_face_detector()
  84. self.predictor = dlib.shape_predictor(facePredictor)
  85. def getAllFaceBoundingBoxes(self, rgbImg):
  86. """
  87. Find all face bounding boxes in an image.
  88. :param rgbImg: RGB image to process. Shape: (height, width, 3)
  89. :type rgbImg: numpy.ndarray
  90. :return: All face bounding boxes in an image.
  91. :rtype: dlib.rectangles
  92. """
  93. assert rgbImg is not None
  94. try:
  95. return self.detector(rgbImg, 1)
  96. except Exception as e: #pylint: disable=broad-except
  97. print("Warning: {}".format(e))
  98. # In rare cases, exceptions are thrown.
  99. return []
  100. def getLargestFaceBoundingBox(self, rgbImg, skipMulti=False):
  101. """
  102. Find the largest face bounding box in an image.
  103. :param rgbImg: RGB image to process. Shape: (height, width, 3)
  104. :type rgbImg: numpy.ndarray
  105. :param skipMulti: Skip image if more than one face detected.
  106. :type skipMulti: bool
  107. :return: The largest face bounding box in an image, or None.
  108. :rtype: dlib.rectangle
  109. """
  110. assert rgbImg is not None
  111. faces = self.getAllFaceBoundingBoxes(rgbImg)
  112. if (not skipMulti and len(faces) > 0) or len(faces) == 1:
  113. return max(faces, key=lambda rect: rect.width() * rect.height())
  114. else:
  115. return None
  116. def findLandmarks(self, rgbImg, bb):
  117. """
  118. Find the landmarks of a face.
  119. :param rgbImg: RGB image to process. Shape: (height, width, 3)
  120. :type rgbImg: numpy.ndarray
  121. :param bb: Bounding box around the face to find landmarks for.
  122. :type bb: dlib.rectangle
  123. :return: Detected landmark locations.
  124. :rtype: list of (x,y) tuples
  125. """
  126. assert rgbImg is not None
  127. assert bb is not None
  128. points = self.predictor(rgbImg, bb)
  129. #return list(map(lambda p: (p.x, p.y), points.parts()))
  130. return [(p.x, p.y) for p in points.parts()]
  131. #pylint: disable=dangerous-default-value
  132. def align(self, imgDim, rgbImg, bb=None,
  133. landmarks=None, landmarkIndices=INNER_EYES_AND_BOTTOM_LIP,
  134. skipMulti=False, scale=1.0):
  135. r"""align(imgDim, rgbImg, bb=None, landmarks=None, landmarkIndices=INNER_EYES_AND_BOTTOM_LIP)
  136. Transform and align a face in an image.
  137. :param imgDim: The edge length in pixels of the square the image is resized to.
  138. :type imgDim: int
  139. :param rgbImg: RGB image to process. Shape: (height, width, 3)
  140. :type rgbImg: numpy.ndarray
  141. :param bb: Bounding box around the face to align. \
  142. Defaults to the largest face.
  143. :type bb: dlib.rectangle
  144. :param landmarks: Detected landmark locations. \
  145. Landmarks found on `bb` if not provided.
  146. :type landmarks: list of (x,y) tuples
  147. :param landmarkIndices: The indices to transform to.
  148. :type landmarkIndices: list of ints
  149. :param skipMulti: Skip image if more than one face detected.
  150. :type skipMulti: bool
  151. :param scale: Scale image before cropping to the size given by imgDim.
  152. :type scale: float
  153. :return: The aligned RGB image. Shape: (imgDim, imgDim, 3)
  154. :rtype: numpy.ndarray
  155. """
  156. assert imgDim is not None
  157. assert rgbImg is not None
  158. assert landmarkIndices is not None
  159. if bb is None:
  160. bb = self.getLargestFaceBoundingBox(rgbImg, skipMulti)
  161. if bb is None:
  162. return
  163. if landmarks is None:
  164. landmarks = self.findLandmarks(rgbImg, bb)
  165. npLandmarks = np.float32(landmarks)
  166. npLandmarkIndices = np.array(landmarkIndices)
  167. #pylint: disable=maybe-no-member
  168. H = cv2.getAffineTransform(npLandmarks[npLandmarkIndices],
  169. imgDim * MINMAX_TEMPLATE[npLandmarkIndices]*scale + imgDim*(1-scale)/2)
  170. thumbnail = cv2.warpAffine(rgbImg, H, (imgDim, imgDim))
  171. return thumbnail