npc0/TestingAI
0
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()