import os from typing import Union, Tuple import base64 from pathlib import Path # 3rd party import numpy as np import cv2 import requests def load_image(img: Union[str, np.ndarray]) -> Tuple[np.ndarray, str]: """ Load image from path, url, base64 or numpy array. Args: img: a path, url, base64 or numpy array. Returns: image (numpy array): the loaded image in BGR format image name (str): image name itself """ # The image is already a numpy array if isinstance(img, np.ndarray): return img, "numpy array" if isinstance(img, Path): img = str(img) if not isinstance(img, str): raise ValueError(f"img must be numpy array or str but it is {type(img)}") # The image is a base64 string if img.startswith("data:image/"): return load_base64(img), "base64 encoded string" # The image is a url if img.lower().startswith("http://") or img.lower().startswith("https://"): return load_image_from_web(url=img), img # The image is a path if os.path.isfile(img) is not True: raise ValueError(f"Confirm that {img} exists") # image must be a file on the system then # image name must have english characters if img.isascii() is False: raise ValueError(f"Input image must not have non-english characters - {img}") img_obj_bgr = cv2.imread(img) # img_obj_rgb = cv2.cvtColor(img_obj_bgr, cv2.COLOR_BGR2RGB) return img_obj_bgr, img def load_image_from_web(url: str) -> np.ndarray: """ Loading an image from web Args: url: link for the image Returns: img (np.ndarray): equivalent to pre-loaded image from opencv (BGR format) """ response = requests.get(url, stream=True, timeout=60) response.raise_for_status() image_array = np.asarray(bytearray(response.raw.read()), dtype=np.uint8) image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) return image def load_base64(uri: str) -> np.ndarray: """Load image from base64 string. Args: uri: a base64 string. Returns: numpy array: the loaded image. """ encoded_data_parts = uri.split(",") if len(encoded_data_parts) < 2: raise ValueError("format error in base64 encoded string") # similar to find functionality, we are just considering these extensions if not ( uri.startswith("data:image/jpeg") or uri.startswith("data:image/jpg") or uri.startswith("data:image/png") ): raise ValueError(f"input image can be jpg, jpeg or png, but it is {encoded_data_parts}") encoded_data = encoded_data_parts[1] nparr = np.fromstring(base64.b64decode(encoded_data), np.uint8) img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) return img_bgr def normalize_input(img: np.ndarray, normalization: str = "base") -> np.ndarray: """Normalize input image. Args: img (numpy array): the input image. normalization (str, optional): the normalization technique. Defaults to "base", for no normalization. Returns: numpy array: the normalized image. """ # issue 131 declares that some normalization techniques improves the accuracy if normalization == "base": return img # @trevorgribble and @davedgd contributed this feature # restore input in scale of [0, 255] because it was normalized in scale of # [0, 1] in preprocess_face img *= 255 if normalization == "raw": pass # return just restored pixels elif normalization == "Facenet": mean, std = img.mean(), img.std() img = (img - mean) / std elif normalization == "Facenet2018": # simply / 127.5 - 1 (similar to facenet 2018 model preprocessing step as @iamrishab posted) img /= 127.5 img -= 1 elif normalization == "VGGFace": # mean subtraction based on VGGFace1 training data img[..., 0] -= 93.5940 img[..., 1] -= 104.7624 img[..., 2] -= 129.1863 elif normalization == "VGGFace2": # mean subtraction based on VGGFace2 training data img[..., 0] -= 91.4953 img[..., 1] -= 103.8827 img[..., 2] -= 131.0912 elif normalization == "ArcFace": # Reference study: The faces are cropped and resized to 112×112, # and each pixel (ranged between [0, 255]) in RGB images is normalised # by subtracting 127.5 then divided by 128. img -= 127.5 img /= 128 else: raise ValueError(f"unimplemented normalization type - {normalization}") return img