diff --git a/README.md b/README.md index 46afe9c..c85753c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Please read the results as follow: Please download the DLIB [Shape Predictor](https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/shape_predictor_68_face_landmarks.dat?download=true) and the [Face Recognition](https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/dlib_face_recognition_resnet_model_v1.dat?download=true) models and place them into the `dlib` directory. +Precompiled Dlib for windows can be found [here](https://github.com/z-mahmud22/Dlib_Windows_Python3.x). + In this repository you also find a workflow that uses IPAdapter to generate a few images and return the distance to the reference face. ![face analysis](./face_analysis.jpg) diff --git a/faceanalysis.py b/faceanalysis.py index 3d25d2b..b96c24e 100644 --- a/faceanalysis.py +++ b/faceanalysis.py @@ -59,9 +59,97 @@ class FaceAnalysisModels: "shape_predict": dlib.shape_predictor(os.path.join(DLIB_DIR, "shape_predictor_68_face_landmarks.dat")), "face_recog": dlib.face_recognition_model_v1(os.path.join(DLIB_DIR, "dlib_face_recognition_resnet_model_v1.dat")), } + return (out, ) +def crop_face(image, x, y, w, h, padding=0): + x = max(0, x - padding) + y = max(0, y - padding) + w = min(image.width, w + 2 * padding) + h = min(image.height, h + 2 * padding) + + return image.crop((x, y, x + w, y + h)) + +class FaceBoundingBox: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "analysis_models": ("ANALYSIS_MODELS", ), + "image": ("IMAGE", ), + "padding": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 1 }), + "index": ("INT", { "default": -1, "min": -1, "max": 4096, "step": 1 }), + }, + } + + RETURN_TYPES = ("IMAGE", "INT", "INT", "INT", "INT") + RETURN_NAMES = ("IMAGE", "x", "y", "width", "height") + FUNCTION = "bbox" + CATEGORY = "FaceAnalysis" + + def bbox(self, analysis_models, image, padding, index=-1): + out_img = [] + out_x = [] + out_y = [] + out_w = [] + out_h = [] + + for i in image: + img = T.ToPILImage()(i.permute(2, 0, 1)).convert('RGB') + + if analysis_models["library"] == "insightface": + faces = analysis_models["detector"].get(np.array(img)) + for face in faces: + x, y, w, h = face.bbox.astype(int) + w = w - x + h = h - y + x = max(0, x - padding) + y = max(0, y - padding) + w = min(img.width, w + 2 * padding) + h = min(img.height, h + 2 * padding) + crop = img.crop((x, y, x + w, y + h)) + out_img.append(T.ToTensor()(crop).permute(1, 2, 0)) + out_x.append(x) + out_y.append(y) + out_w.append(w) + out_h.append(h) + + else: + faces = analysis_models["detector"](np.array(img), 1) + for face in faces: + x, y, w, h = face.left(), face.top(), face.width(), face.height() + x = max(0, x - padding) + y = max(0, y - padding) + w = min(img.width, w + 2 * padding) + h = min(img.height, h + 2 * padding) + crop = img.crop((x, y, x + w, y + h)) + out_img.append(T.ToTensor()(crop).permute(1, 2, 0)) + out_x.append(x) + out_y.append(y) + out_w.append(w) + out_h.append(h) + + if not out_img: + raise Exception('No face detected in image.') + + out_img = torch.stack(out_img) + + if out_img.shape[0] == 1: + index = 0 + + if index > out_img.shape[0] - 1: + index = out_img.shape[0] - 1 + + if index != -1: + out_img = out_img[index].unsqueeze(0) + out_x = out_x[index] + out_y = out_y[index] + out_w = out_w[index] + out_h = out_h[index] + + return (out_img, out_x, out_y, out_w, out_h,) + class FaceEmbedDistance: @classmethod def INPUT_TYPES(s): @@ -173,9 +261,11 @@ class FaceEmbedDistance: NODE_CLASS_MAPPINGS = { "FaceEmbedDistance": FaceEmbedDistance, "FaceAnalysisModels": FaceAnalysisModels, + "FaceBoundingBox": FaceBoundingBox, } NODE_DISPLAY_NAME_MAPPINGS = { "FaceEmbedDistance": "Face Embeds Distance", "FaceAnalysisModels": "Face Analysis Models", + "FaceBoundingBox": "Face Bounding Box", }