CoolFace
Apppublic

npc0/TestingAI

sourceHugging Facemitupdated 3y agoView on Hugging Face
0likes
faceSym.py294 linesDownload Raw Back to root
1# based on eggplants/face-symmetrizer2from __future__ import annotations3 4import io5import re6from copy import copy7from os import path8from typing import Any, Dict, List, Tuple9from urllib.request import urlopen10 11import face_recognition  # type: ignore[import]12import numpy as np13from PIL import Image, ImageDraw, ImageOps14 15PILImage = Image.Image16FaceLandmarks = List[Dict[str, List[Tuple[Any, ...]]]]17 18 19class FaceIsNotDetected(Exception):20    """[summary]21 22    Args:23        Exception ([type]): [description]24    """25 26    pass27 28 29class FaceSym:30    """[summary]"""31 32    SimImages = Tuple[PILImage, PILImage, PILImage, PILImage, PILImage, PILImage]33 34    def __init__(self, img_location: str) -> None:35        """[summary]36 37        Args:38            img_location (str): [description]39 40        Raises:41            ValueError: [description]42        """43        self.f_img: np.ndarray[Any, Any]44        self.image_location = img_location45        if self.__is_valid_url(img_location):46            self.__load_from_url(img_location)47        elif path.isfile(img_location):48            self.__load_from_local(img_location)49        else:50            raise ValueError(51                f"{repr(img_location)} is not a valid location of an image."52            )53 54        self.f_img_PIL = Image.fromarray(self.f_img)55        self.image_size: tuple[int, int] = self.f_img_PIL.size56        self.face_locations = face_recognition.face_locations(self.f_img)57        self.face_landmarks = face_recognition.face_landmarks(self.f_img)58        self.mid_face_locations = self.__get_mid_face_locations(self.face_landmarks)59        self.face_count = len(self.face_locations)60 61    def get_cropped_face_images(self,) -> list[PILImage]:62        """[summary]63 64        Returns:65            List[PILImage]: [description]66        """67        images = []68        for face_location in self.face_locations:69            top, right, bottom, left = face_location70            cropped_face_img = self.f_img[top:bottom, left:right]71            pil_img = Image.fromarray(cropped_face_img)72            73            images.append(pil_img)74 75        return images76 77    def get_face_box_drawed_image(self) -> PILImage:78        """[summary]79 80        Returns:81            PILImage: [description]82        """83        pil = copy(self.f_img_PIL)84        draw = ImageDraw.Draw(pil)85        for idx, (top, right, bottom, left) in enumerate(self.face_locations):86            name = str(f"{idx:02d}")87            mid_face = self.mid_face_locations[idx]88 89            draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255))90 91            _, text_height = draw.textsize(name)92            draw.rectangle(93                ((left, bottom - text_height - 10), (right, bottom)),94                fill=(0, 0, 255),95                outline=(0, 0, 255),96            )97            draw.text((left + 6, bottom - text_height - 5), name, fill=(255, 255, 255))98 99            draw.line(100                ((mid_face[0], -10), mid_face, (mid_face[0], self.image_size[0])),101                fill=(255, 255, 0),102                width=10,103            )104        del draw105        return pil106 107    def get_full_image(108        self, is_pil: bool = False109    ) -> np.ndarray[Any, Any] | PILImage:110        """[summary]111 112        Args:113            is_pil (bool, optional): [description]. Defaults to False.114 115        Returns:116            Union[np.ndarray, PILImage]: [description]117        """118        119        if is_pil:120            return self.f_img_PIL121        else:122            return self.f_img123 124    def get_symmetrized_images(self, idx: int = 0) -> SimImages:125        """[summary]126 127        Args:128            idx (int, optional): [description]. Defaults to 0.129 130        Returns:131            SimImages: [description]132        """133 134        def get_concat_h(im1: PILImage, im2: PILImage) -> PILImage:135            dst = Image.new("RGB", (im1.width + im2.width, im1.height))136            dst.paste(im1, (0, 0))137            dst.paste(im2, (im1.width, 0))138            return dst139 140        face_count = len(self.mid_face_locations)141        if face_count < 1:142            raise FaceIsNotDetected143        elif face_count <= idx:144            raise IndexError(f"0 <= idx <= {face_count - 1}")145        else:146            mid_face = self.mid_face_locations[idx]147 148        cropped_left_img = self.f_img[0 : self.image_size[1], 0 : int(mid_face[0])]149        cropped_right_img = self.f_img[150            0 : self.image_size[1], int(mid_face[0]) : self.image_size[0]151        ]152 153        pil_img_left = Image.fromarray(cropped_left_img)154        pil_img_left_mirrored = ImageOps.mirror(pil_img_left)155        pil_img_left_inner = get_concat_h(pil_img_left, pil_img_left_mirrored)156        pil_img_left_outer = get_concat_h(pil_img_left_mirrored, pil_img_left)157 158        pil_img_right = Image.fromarray(cropped_right_img)159        pil_img_right_mirrored = ImageOps.mirror(pil_img_right)160        pil_img_right_inner = get_concat_h(pil_img_right_mirrored, pil_img_right)161        pil_img_right_outer = get_concat_h(pil_img_right, pil_img_right_mirrored)162 163        return (164            pil_img_left,165            pil_img_left_inner,166            pil_img_left_outer,167            pil_img_right,168            pil_img_right_inner,169            pil_img_right_outer,170        )171 172    def __load_from_url(self, url: str) -> None:173        """[summary]174 175        Args:176            url (str): [description]177 178        Raises:179            ValueError: [description]180        """181        if not self.__is_valid_url(url):182            raise ValueError(f"{repr(url)} is not valid url")183        else:184            img_data = io.BytesIO(urlopen(url).read())185            self.f_img = face_recognition.load_image_file(img_data)186 187    def __load_from_local(self, path_: str) -> None:188        if path.isfile(path_):189            self.f_img = face_recognition.load_image_file(path_)190 191    @staticmethod192    def __is_valid_url(url: str) -> bool:193        """[summary]194 195        Args:196            url (str): [description]197 198        Returns:199            bool: [description]200 201        Note:202            Copyright (c) Django Software Foundation and individual203            contributors. All rights reserved.204        """205        regex = re.compile(206            r"^(?:http|ftp)s?://"  # http:// or https://207            # domain...208            r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|"209            r"[A-Z0-9-]{2,}\.?)|"210            r"localhost|"  # localhost...211            r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"  # ...or ip212            r"(?::\d+)?"  # optional port213            r"(?:/?|[/?]\S+)$",214            re.IGNORECASE,215        )216        return re.match(regex, url) is not None217 218    @staticmethod219    def __get_mid_face_locations(220        face_landmarks: FaceLandmarks,221    ) -> list[tuple[int, int]]:222        """[summary]223 224        Args:225            face_landmarks (FaceLandmarks): [description]226 227        Returns:228            List[Tuple[int, int]]: [description]229        """230 231        def mean(lst: list[int]) -> int:232            return int(sum(lst) / len(lst))233 234        mid_faces = []235        for face_landmark in face_landmarks:236            if not ("left_eye" in face_landmark and "right_eye" in face_landmark):237                raise ValueError("eye locations was missing.")238            l_e_xs = [i[0] for i in face_landmark["left_eye"]]239            l_e_ys = [i[1] for i in face_landmark["left_eye"]]240            r_e_xs = [i[0] for i in face_landmark["right_eye"]]241            r_e_ys = [i[1] for i in face_landmark["right_eye"]]242            mid_face = (243                (mean(l_e_xs) + mean(r_e_xs)) // 2,244                (mean(l_e_ys) + mean(r_e_ys)) // 2,245            )246            mid_faces.append(mid_face)247        return mid_faces248 249 250def main() -> None:251    """[summary]"""252    data = list(253        map(254            lambda x: "https://pbs.twimg.com/media/%s?format=jpg" % x,255            [256                "E7okHDEVUAE1O6i",257                "E7jaibgUcAUWvg-",258                "E7jahEbUcAMNLdU",259                "E7Jqli9VEAEStvs",260                "E7Jqk-aUcAcfg3o",261                "E7EhGi2XoAsMrO5",262                "E5dhLccUYAUD5Yx",263                "E5TOAqUVUAMckXT",264                "E4vK6e0VgAAksnK",265                "E4Va7u4VkAAKde3",266                "E4A0ksEUYAIpynP",267                "E3xXzcyUYAIX1dC",268                "E2zkvONVcAQEE_S",269                "E1cBsxDUcAIe_LZ",270                "E1W4HTRVUAgYkmo",271                "E1HbVAeVIAId5yP",272                "E09INVFUcAYpcWo",273                "E0oh0hmUUAAfJV9",274            ],275        )276    )277    success, fail = 0, 0278    for idx, link in enumerate(data):279        print(f"[{idx:02d}]", link, end="")280        f = FaceSym(link)281        if f.face_count != 0:282            print("=>Detected")283            f.get_symmetrized_images()284            success += 1285        else:286            print("=>Not Detected")287            fail += 1288 289    else:290        print(f"DATA: {len(data)}", f"OK: {success}", f"NG: {fail}")291 292 293if __name__ == "__main__":294    main()