Skip to content

Instantly share code, notes, and snippets.

@kiyoshi-tokunaga
Last active January 4, 2019 10:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kiyoshi-tokunaga/9e00d61e767561eb7df4 to your computer and use it in GitHub Desktop.
Save kiyoshi-tokunaga/9e00d61e767561eb7df4 to your computer and use it in GitHub Desktop.
# 参考サイト
# https://gist.github.com/parosky/7890436
# https://github.com/nagadomi/lbpcascade_animeface

import cv2
import numpy as np


def unique(a):
    """ remove duplicate columns and rows
        from http://stackoverflow.com/questions/8560440 """
    order = np.lexsort(a.T)
    a = a[order]
    diff = np.diff(a, axis=0)
    ui = np.ones(len(a), 'bool')
    ui[1:] = (diff != 0).any(axis=1)
    return a[ui]


def skin_detection(image, haarcascades, flood_diff=3, min_face_size=(30,30),
        num_iter=3, verbose=False, step=1):
    faces = haarcascades.detectMultiScale(image, minSize=min_face_size)
    if len(faces) == 0:
        raise Exception('no faces')

    image_original = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    image2 = np.copy(image_original)
    image2[image2==[0,0,0]] = 1

    skin_color = np.zeros((len(faces), 3))
    for i, face in enumerate(faces):
        image_face = image2[face[1]:face[1]+face[3],face[0]:face[0]+face[2]]
        skin_color[i] = np.array([image_face[image_face.shape[0]/2, image_face.shape[1]/2]])

    mask = np.zeros(image_original.shape)
    for i in range(num_iter):
        # for each pixel, call floodFill(image2) if it is close to skin_color
        for y in range(0, image2.shape[0], step):
            if verbose:
                print('iter: %d, y:%d' % (i, y))
            for x in range(0, image2.shape[1], step):
                color = image2[y,x]
                if (color!=(0,0,0)).any():
                    if any((np.abs(skin_color-color)<=(flood_diff,)*3).all(1)):
                        cv2.floodFill(image2, None, (x,y), (0, 0, 0),
                                loDiff=(flood_diff,)*3, upDiff=(flood_diff,)*3)

        # update mask image and skin_color
        mask[image2==(0,0,0)] = 255
        skin_color = image_original[mask.nonzero()]
        skin_color.shape = (skin_color.shape[0]/3, 3)
        skin_color = unique(skin_color)

    mask = np.bool_(mask)
    return mask


def blown_skin_mask(im, mask):
    height, width = im.shape[:2]
    hsv_im = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    for y in range(height):
        for x in range(width):
            if (mask[y, x, 0]):
                h = hsv_im[y, x, 0]
                s = hsv_im[y, x, 1]
                if (h < 8):
                    hsv_im[y, x, 0] = h + 180 - 8
                else:
                    hsv_im[y, x, 0] -= 8
                if (s < 212):
                    hsv_im[y, x, 1] += 43
                else:
                    hsv_im[y, x, 1] = 255
    return cv2.cvtColor(hsv_im, cv2.COLOR_HSV2BGR)


if __name__ == '__main__':
    filename = 'test.jpg'
    filename_out = (''.join(filename.split('.')[:-1])
                    + '_out%d.' + filename.split('.')[-1])
    image = cv2.imread(filename)
    hc = cv2.CascadeClassifier('lbpcascade_animeface.xml')

    mask = skin_detection(image, hc, num_iter=2, verbose=True)
    blown_im = blown_skin_mask(image, mask)
    cv2.imwrite(filename_out % 1, blown_im)
    cv2.imwrite(filename_out % 2, np.uint8(mask)*255)
    concat_im = np.concatenate((image, blown_im), axis=1)
    cv2.imwrite(filename_out % 3, concat_im)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment