Source code for neurokit2.video.video_skin

import numpy as np

from ..misc import find_closest


[docs] def video_skin(face, show=False): """**Skin detection** This function detects the skin in a face. .. note:: This function is experimental. If you are interested in helping us improve that aspect of NeuroKit (e.g., by adding more detection algorithms), please get in touch! Parameters ---------- face : np.ndarray A face data numpy array of the shape (channel, height, width). show : bool Whether to show the skin detection mask. Returns ------- np.ndarray A skin detection mask. See Also -------- video_face, video_ppg Examples -------- .. ipython:: python import neurokit2 as nk # video, sampling_rate = nk.read_video("video.mp4") # faces = nk.video_face(video) # skin = nk.video_skin(faces[0], show=True) """ # Try loading cv2 try: import cv2 except ImportError: raise ImportError( "The 'cv2' module is required for this function to run. ", "Please install it first (`pip install opencv-python`).", ) img = face.swapaxes(0, 1).swapaxes(1, 2) # Credits: # https://github.com/pavisj/rppg-pos/blob/master/SkinDetector/skin_detector/skin_detector.py # Get mask in HSV space img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) lower_thresh = np.array([0, 50, 0], dtype="uint8") upper_thresh = np.array([120, 150, 255], dtype="uint8") mask_hsv = cv2.inRange(img_hsv, lower_thresh, upper_thresh) mask_hsv[mask_hsv < 128] = 0 mask_hsv[mask_hsv >= 128] = 1 # Get mask in RGB space lower_thresh = np.array([45, 52, 108], dtype="uint8") upper_thresh = np.array([255, 255, 255], dtype="uint8") mask_a = cv2.inRange(img, lower_thresh, upper_thresh) mask_b = 255 * ((img[:, :, 2] - img[:, :, 1]) / 20) mask_c = 255 * ((np.max(img, axis=2) - np.min(img, axis=2)) / 20) mask_d = np.bitwise_and(np.uint64(mask_a), np.uint64(mask_b)) mask_rgb = np.bitwise_and(np.uint64(mask_c), np.uint64(mask_d)) mask_rgb[mask_rgb < 128] = 0 mask_rgb[mask_rgb >= 128] = 1 # Get mask in YCbCr space lower_thresh = np.array([90, 100, 130], dtype="uint8") upper_thresh = np.array([230, 120, 180], dtype="uint8") img_ycrcb = cv2.cvtColor(img, cv2.COLOR_RGB2YCR_CB) mask_ycrcb = cv2.inRange(img_ycrcb, lower_thresh, upper_thresh) mask_ycrcb[mask_ycrcb < 128] = 0 mask_ycrcb[mask_ycrcb >= 128] = 1 mask = (mask_hsv + mask_rgb + mask_ycrcb) / 3 # Get percentages of skin as a function of different thresholds threshold = np.arange(0, 1.2, 0.3) percent = [np.sum(mask >= t) / mask.size for t in threshold] threshold = threshold[find_closest(0.5, percent, return_index=True)] mask[mask < threshold] = 0 mask[mask >= threshold] = 255 # Process mask kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2) # Grab and cut kernel = np.ones((50, 50), np.float32) / (50 * 50) dst = cv2.filter2D(mask, -1, kernel) dst[dst != 0] = 255 free = np.array(cv2.bitwise_not(dst), dtype="uint8") grab_mask = np.zeros(mask.shape, dtype="uint8") grab_mask[:, :] = 2 grab_mask[mask == 255] = 1 grab_mask[free == 255] = 0 if np.unique(grab_mask).tolist() == [0, 1]: bgdModel = np.zeros((1, 65), np.float64) fgdModel = np.zeros((1, 65), np.float64) if img.size != 0: mask, bgdModel, fgdModel = cv2.grabCut( img, grab_mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK ) mask = np.where((mask == 2) | (mask == 0), 0, 1).astype("uint8") mask = mask.astype("uint8") masked_face = cv2.bitwise_and(img, img, mask=mask) if show is True: print(f"{int((100 / 255) * np.sum(mask) / mask.size)}% of the image is skin") cv2.imshow("img", cv2.cvtColor(mask.astype("uint8"), cv2.COLOR_RGB2BGR)) cv2.waitKey(0) return mask, masked_face