Пример #1
0
    def find_mustache(self):
        """Try to find suitable mustache position and return if successful."""
        if self.face is not None and \
                (self.nose is not None or self.mouth is not None):

            # We can use nose, mouth or a combination to find the right spot
            # for a mustache.
            if self.mouth is not None and self.nose is not None:
                x = (self.mouth.center.x + self.nose.center.x) / 2
                y = (2 * self.nose.center.y + self.mouth.center.y) / 3
                width = max(self.nose.width, self.mouth.width)
                height = abs(self.nose.center.y - self.mouth.center.y) / 3
            else:
                chosen = self.mouth or self.nose
                x = chosen.center.x
                y = chosen.bottom if chosen == self.nose else chosen.top
                width = chosen.width
                height = 20

            left = int(x - .5 * width)
            right = int(x + .5 * width)
            top = int(y - .5 * height)
            bottom = int(y + .5 * height)

            self.mustache = Rect(left, top, right - left, bottom - top)

            # Apply filter on position relative towards face position
            if self.moustache_filter is not None:
                rel = self.mustache.translate(-self.face.center.x,
                                              -self.face.center.y)
                self.moustache_filter.correct(
                    np.array([rel.left, rel.right, rel.top, rel.bottom]))
                left, right, top, bottom = self.moustache_filter.predict()
                rel = Rect(left, top, right - left, bottom - top)
                self.mustache = rel.translate(self.face.center.x,
                                              self.face.center.y)

            return True
        else:
            # Since we might have lost track of mustache completely, we forget
            # it ever existed.
            self.mustache = None
            self.reset_filter()
            return False
Пример #2
0
class MustacheRecognizer(object):
    """Model of mustache location

    Actually we want the place to put mustache and assume there is no existing
    one."""
    def __init__(self, black=False):
        """Initialize recognizer.

        black - whether the mustache should be drawn in black. Otherwise hair
        colour is recognised."""
        self.load_resources()
        self.reset()
        self.black = black

    def reset(self):
        """Reset the state the mustache detector is in"""
        self.frame = None
        self.face = None
        self.nose = None
        self.mouth = None
        self.mustache = None
        self.eyes = [None, None]
        self.rotation = 0
        self.skin_color = None
        self.skin_std = None
        self.hair_color = None

        self.reset_filter()

        self.step = 0
        self.features = [
            self.find_face, self.find_nose, self.find_mustache,
            self.find_mouth, self.find_eyes
        ]

        self.nose_timeout = 0
        self.mouth_timeout = 0

    def reset_filter(self):
        """Reset kalman filter."""
        self.moustache_filter = SimpleKalmanFilter(.1)

    def load_resources(self):
        """Load cascades used for recognition and image of mustache."""
        self.mustache_image = cv2.imread(MUSTACHE, -1)

        self.faceCascade = cv2.CascadeClassifier(FACECASCADE)
        self.noseCascade = cv2.CascadeClassifier(NOSECASCADE)
        self.mouthCascade = cv2.CascadeClassifier(MOUTHCASCADE)
        self.eyeCascade = cv2.CascadeClassifier(EYECASCADE)

    def update_camera(self):
        """Read next camera frame and store it."""
        self.frame = self.cam.read()[1]
        return self.frame

    def find_face(self):
        """Perform face recognition and return if successful"""
        if self.frame is not None:
            faces = self.faceCascade.detectMultiScale(
                self.frame,
                1.2,
                1,
                minSize=(100, 100),
                flags=cv2.cv.CV_HAAR_DO_CANNY_PRUNING)
            if len(faces) > 0:
                face = faces[0]
                self.face = Rect._make(face)
                assert (face is not None)
                return True

        return False

    def find_nose(self):
        """Perform nose recognition and return if successful."""
        if self.face is not None:
            # Specify region of interest
            left = self.face.left + self.face.width / 4
            right = self.face.right - self.face.width / 4
            top = self.face.top + self.face.height / 6
            bottom = self.face.bottom - self.face.height / 6

            # Nose should be above the mouth
            if self.mouth is not None:
                bottom = self.mouth.top

            # Nose should be below the eyes
            for eye in self.eyes:
                if eye is not None:
                    top = max(top, eye.bottom)

            # Search the nose
            face_patch = self.frame[top:bottom, left:right]
            if face_patch.size > 0:
                noses = self.noseCascade.detectMultiScale(
                    face_patch,
                    1.2,
                    1,
                    minSize=(10, 10),
                    maxSize=(self.face[2] / 4, self.face[3] / 2),
                    flags=cv2.cv.CV_HAAR_DO_CANNY_PRUNING)

                # Convert the found nose back to absolute coordinates
                if (len(noses) > 0):
                    self.nose = Rect._make(noses[0]).translate(left, top)
                    return True

        # If we didn't find anything
        if self.nose_timeout > 0:
            self.nose_timeout -= 1
        else:
            self.nose = None
            self.nose_timeout = 10

        return False

    def find_mouth(self):
        """Perform mouth recognition and return if successful."""
        if self.face is not None:

            # Specify region of interest
            left = self.face.left + self.face.width / 3
            right = self.face.right - self.face.width / 3
            top = self.face.top + self.face.height / 2
            bottom = self.face.bottom

            # Mouth should be below the nose
            if self.nose is not None:
                top = self.nose.bottom

            face_patch = self.frame[top:bottom, left:right]

            # Mouth should be wider than nose
            if self.nose is not None:
                minwidth = int(.7 * self.nose.width)
            else:
                minwidth = 30

            # Search for the mouth
            if face_patch.size > 0:
                mouths = self.mouthCascade.detectMultiScale(face_patch,
                                                            1.2,
                                                            1,
                                                            minSize=(minwidth,
                                                                     5))

                # Convert the result
                if len(mouths) > 0:
                    self.mouth = Rect._make(mouths[0]).translate(left, top)
                    return True

        # If we didn't find anything
        if self.mouth_timeout > 0:
            self.mouth_timeout -= 1
        else:
            self.mouth = None
            self.mouth_timeout = 10
        return False

    def find_mustache(self):
        """Try to find suitable mustache position and return if successful."""
        if self.face is not None and \
                (self.nose is not None or self.mouth is not None):

            # We can use nose, mouth or a combination to find the right spot
            # for a mustache.
            if self.mouth is not None and self.nose is not None:
                x = (self.mouth.center.x + self.nose.center.x) / 2
                y = (2 * self.nose.center.y + self.mouth.center.y) / 3
                width = max(self.nose.width, self.mouth.width)
                height = abs(self.nose.center.y - self.mouth.center.y) / 3
            else:
                chosen = self.mouth or self.nose
                x = chosen.center.x
                y = chosen.bottom if chosen == self.nose else chosen.top
                width = chosen.width
                height = 20

            left = int(x - .5 * width)
            right = int(x + .5 * width)
            top = int(y - .5 * height)
            bottom = int(y + .5 * height)

            self.mustache = Rect(left, top, right - left, bottom - top)

            # Apply filter on position relative towards face position
            if self.moustache_filter is not None:
                rel = self.mustache.translate(-self.face.center.x,
                                              -self.face.center.y)
                self.moustache_filter.correct(
                    np.array([rel.left, rel.right, rel.top, rel.bottom]))
                left, right, top, bottom = self.moustache_filter.predict()
                rel = Rect(left, top, right - left, bottom - top)
                self.mustache = rel.translate(self.face.center.x,
                                              self.face.center.y)

            return True
        else:
            # Since we might have lost track of mustache completely, we forget
            # it ever existed.
            self.mustache = None
            self.reset_filter()
            return False

    def find_eyes(self):
        """Try to detect eyes and return if successful."""
        if self.face is None:
            return False

        # Only search eyes in the upper half of the face
        bottom = self.face.top + 1. / 2. * self.face.height

        if self.nose:
            bottom = min(bottom, self.nose.top)

        # If no eyes at all
        if self.eyes[0] is None and self.eyes[1] is None:

            # Search them both
            face_patch = self.frame[self.face.top:bottom,
                                    self.face.left:self.face.right]
            eyes = self.eyeCascade.detectMultiScale(face_patch,
                                                    1.2,
                                                    2,
                                                    minSize=(5, 2),
                                                    maxSize=(200, 200))

            # We found 2 eyes. Sort eyes by x coordinate
            if len(eyes) >= 2:
                eyes = [
                    Rect._make(eye).translate(dx=self.face.left,
                                              dy=self.face.top) for eye in eyes
                ]
                if eyes[0].center.x > eyes[1].center.x:
                    eyes[0], eyes[1] = eyes[1], eyes[0]
                self.eyes = eyes
                return True

            # We found an eye, but which one?
            # Let's compare its position to the face center
            elif len(eyes) == 1:
                eye = Rect._make(eyes[0]).translate(self.face.left,
                                                    self.face.top)
                if eye.center.x < self.face.center.x:
                    self.eyes[0] = eye
                else:
                    self.eyes[1] = eye
                return True

            else:
                return False

        else:
            # Handle both eyes on their own
            self.find_single_eye(0)
            self.find_single_eye(1)

    def find_single_eye(self, index):
        """Find one of the eyes.

        index = 0 => left eye
        index = 1 => right eye
        ."""
        assert (0 <= index < 2)

        if self.face is None:
            return False

        # Set search region
        top = self.face.top
        bottom = self.face.top + 1. / 2. * self.face.height
        left = self.face.left
        right = self.face.right

        if self.nose:
            bottom = self.nose.top

        # Adapt search region for individual eyes
        if index == 1:
            left = self.face.center.x
            if self.eyes[0] is not None:
                left = max(left, self.eyes[0].right)
        else:
            right = self.face.center.x
            if self.eyes[1] is not None:
                right = min(right, self.eyes[1].left)

        face_patch = self.frame[top:bottom, left:right]
        if left < right:
            eyes = self.eyeCascade.detectMultiScale(face_patch,
                                                    1.2,
                                                    1,
                                                    minSize=(5, 2),
                                                    maxSize=(200, 200))
        else:
            return False

        if len(eyes) > 0:
            eye = eyes[0]

            self.eyes[index] = Rect._make(eye).translate(left, self.face.top)
            return True
        else:
            return False

    def get_rotation_from_eyes(self):
        """Determine rotation from face.

        Use positions of eyes to get it."""
        if self.eyes[0] is not None and self.eyes[1] is not None:
            p1 = self.eyes[0].center
            p2 = self.eyes[1].center
            rotation = -math.degrees(math.atan2(p2.y - p1.y, p2.x - p1.x))
            self.rotation = rotation
            return rotation

    def get_skin_color(self):
        """Approximate persons (average) skin color."""
        if self.nose:
            nose = self.frame[self.nose.top:self.nose.bottom,
                              self.nose.left:self.nose.right]

            # Determine average color
            self.skin_color = [
                int(nose[:, :, color].mean()) for color in [0, 1, 2]
            ]
            self.skin_std = [
                int(nose[:, :, color].std()) for color in [0, 1, 2]
            ]

    def get_hair_color(self):
        """Approximate the persons (average) hair color."""
        if self.face and self.skin_color is not None:
            hair = self.frame[self.face.top -
                              self.face.height / 50:self.face.top +
                              self.face.height / 50,
                              self.face.left:self.face.right]

            probable = np.any(
                hair[:, :] <
                np.array(self.skin_color) - 2 * np.array(self.skin_std), 2)
            if probable.size > 0 and hair.size > 0:
                hair = hair[probable]
            self.hair_color = [hair[:, color].mean() for color in [0, 1, 2]]
            return True
        else:
            return False

    def draw_mustache(self):
        """Draws a mustache on the found location"""
        if self.mustache is not None:
            _, _, width, height = self.mustache
            scaled_stache = cv2.resize(self.mustache_image,
                                       (int(width), int(height)))

            # Drawing the mustache is trickier than it seems
            if scaled_stache is not None:
                self.get_rotation_from_eyes()
                rotated_stache = ndimage.interpolation.rotate(scaled_stache,
                                                              self.rotation,
                                                              axes=(0, 1),
                                                              order=0,
                                                              reshape=True)

                # The fourth channel is the alpha value. I consider all alpha
                # values above 0.5 to be visible.
                visible = (rotated_stache[:, :, 3] > .5).nonzero()

                # Translate the visible positions towards the position where
                # the mustache should be placed
                visible2 = tuple(visible + np.array(
                    [self.mustache.top, self.mustache.left]).reshape(2, 1))

                # There is a very small probability that it will try drawing
                # the mustache out of the window.
                try:
                    if rotated_stache is not None:
                        if self.hair_color:
                            self.output[visible2] = np.array(
                                self.hair_color).reshape(1, 3)
                        else:
                            self.output[visible2] = rotated_stache[:, :, :3][
                                visible]
                except Exception, e:
                    sys.stderr.write(
                        "I can't draw a mustache at that location\n")
                    sys.stderr.write("{}\n".format(e))