Source code for profile_photo.utils.aws.rekognition_utils

from __future__ import annotations

__all__ = ['FillColor',
           'draw_circle',
           'draw_rectangle',
           'draw_box',
           'show_image',
           'best_fit_coordinates']

from enum import Enum
from functools import cached_property

import cv2 as cv
from PIL import Image, ImageDraw

from .rekognition_models import BoundingBox, Landmark, Coordinates
from ...log import LOG


_DEFAULT_OFFSET = 0.17

_SCREEN_WIDTH = _SCREEN_HEIGHT = None


[docs]class FillColor(Enum): GREEN = '#00d400' RED = '#ff0000' YELLOW = '#ffff00' BLUE = '#0000ff' BLACK = '#000000' WHITE = '#FFFFFF' @cached_property def bgr(self): """Returns the BGR representation of the color.""" return hex_to_bgr(self.value)
def get_screen_height_and_width(): global _SCREEN_HEIGHT global _SCREEN_WIDTH if _SCREEN_HEIGHT is None or _SCREEN_WIDTH is None: import tkinter as tk root = tk.Tk() _SCREEN_HEIGHT = root.winfo_screenheight() _SCREEN_WIDTH = root.winfo_screenwidth() return _SCREEN_HEIGHT, _SCREEN_WIDTH def hex_to_bgr(h: str): """ Converts a hex value (i.e. "#ffff00") to a BGR value (i.e. the inverse of RGB). Ref: https://stackoverflow.com/a/29643643/10237506 """ return tuple(int(h.lstrip('#')[i:i + 2], 16) for i in (4, 2, 0))
[docs]def draw_circle(im, landmark: Landmark, color: FillColor = FillColor.YELLOW, radius: int = 3, filled=True, font_scale=0.4, font_face=cv.FONT_HERSHEY_TRIPLEX, text: str = None, text_color: FillColor = FillColor.WHITE): """ Draw a (filled) circle on the image centered around the specified (x, y) coordinates for `landmark` and with the specified border `color` and radius `radius`. Ref: https://www.pyimagesearch.com/2021/01/27/drawing-with-opencv/ Usage:: >>> img = cv.imread('path/to/file.jpeg') >>> draw_circle(img, data['FaceDetails'][0]['Landmarks'][0], \ FillColor.YELLOW, 3, False) """ h, w = im.shape[:2] x = round(landmark.x * w) y = round(landmark.y * h) thickness = cv.FILLED if filled else None cv.circle(im, (x, y), radius, color.bgr, thickness) if text: cv.putText(im, text, (x, y), font_face, font_scale, text_color.bgr)
[docs]def best_fit_coordinates(im, face_box: BoundingBox, *boxes: BoundingBox | None, fit=1/3.5, x_offset=_DEFAULT_OFFSET, y_offset=_DEFAULT_OFFSET, constrain_width=True) -> Coordinates: # recall: reducing by `offset` will enlarge it f_top = face_box.top new_top = f_top - y_offset # calculate left and right (X) coordinates f_left = face_box.left f_right = face_box.left + face_box.width for box in boxes: if not box: continue # if top is not high enough, adjust # if ( L(face) - offset ) is *less* than or close enough to L(box), then # we don't do anything - same with right. b_top = box.top diff = new_top - b_top if diff >= 0: LOG.info('Enlarging Top, top=%.2f, new_top=%.2f', f_top, b_top) face_box.top = f_top = b_top new_top = f_top - y_offset face_box.height += diff + y_offset b_left = box.left b_right = b_left + box.width area_left = abs(f_left - b_left) area_right = abs(b_right - f_right) threshold_left = b_left + fit * area_left threshold_right = b_right - fit * area_right needs_fit = (f_left - x_offset) > threshold_left and (f_right + x_offset) < threshold_right # now left and right if needs_fit: offset_l = f_left - threshold_left offset_r = threshold_right - f_right x_offset = max(offset_l, offset_r) LOG.info('Enlarging Width, new_offset=%.3f', x_offset) if constrain_width: out_of_box = b_left > f_left - x_offset and b_right < f_right + x_offset if out_of_box: LOG.info('Constraining Width for Face') face_box.left = b_left + x_offset face_box.width = box.width - 2 * x_offset face_coords = Coordinates.from_box(im, face_box, x_offset, y_offset) return face_coords
[docs]def draw_rectangle(im, coords_or_box: Coordinates | BoundingBox, color: FillColor = FillColor.GREEN, offset=0, y_offset=0, line_width=3): """ Draw a bounding box on the image at the specified coordinates `box` and with the specified border `color` and `line_width`. An optional `offset` can be provided to enhance or "zoom out" on the box coordinates. Ref: https://stackoverflow.com/questions/23720875/how-to-draw-a-rectangle-around-a-region-of-interest-in-python Usage:: >>> img = cv.imread('path/to/file.jpeg') >>> draw_rectangle(img, data['FaceDetails'][0]['BoundingBox'], FillColor.YELLOW) """ c = Coordinates.from_box(im, coords_or_box, offset, y_offset) \ if isinstance(coords_or_box, BoundingBox) else coords_or_box cv.rectangle( im, (c.x1, c.y1), (c.x2, c.y2), color.bgr, line_width, )
[docs]def draw_box(image: Image, box: BoundingBox, color: FillColor = FillColor.GREEN, line_width=3, offset=0): """ Draw a bounding box on the image at the specified coordinates `box` and with the specified border `color` and `line_width`. An optional `offset` can be provided to change box coordinates. For example, an offset of `line_width + line_width` can be used to create an "inner" bounding box. Displays the image after the box has been drawn. Ref: https://docs.aws.amazon.com/rekognition/latest/dg/images-displaying-bounding-boxes.html Usage:: >>> im = Image.open('path/to/file.jpeg') >>> draw_box(im, data['FaceDetails'][0]['BoundingBox'], FillColor.YELLOW) """ draw = ImageDraw.Draw(image) im_width, im_height = image.size # draw a colored bounding box left = im_width * box.left top = im_height * box.top width = im_width * box.width height = im_height * box.height points = ( (left + offset, top + offset), (left + width - offset, top + offset), (left + (width - offset), (top - offset) + height), (left + offset, top + (height - offset)), (left + offset, top + offset) ) draw.line(points, fill=color.value, width=line_width) image.show()
[docs]def show_image(name, img, area=0.5, window_h=0, window_w=0): """ Displays an image after resizing it to the specified dimensions, while also ensuring that we retain its aspect ratio. Credits: https://stackoverflow.com/a/67718163/10237506 """ import math h, w = img.shape[:2] screen_h, screen_w = get_screen_height_and_width() # import tkinter as tk # root = tk.Tk() # screen_h = root.winfo_screenheight() # screen_w = root.winfo_screenwidth() if area: vector = math.sqrt(area) window_h = screen_h * vector window_w = screen_w * vector if h > window_h or w > window_w: if h / window_h >= w / window_w: multiplier = window_h / h else: multiplier = window_w / w img = cv.resize(img, (0, 0), fx=multiplier, fy=multiplier) cv.imshow(name, img)