123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- import colorsys
- from enum import Enum
- from functools import reduce
- from PIL import ImageDraw, ImageFont, Image
- from keras import backend as K
- import numpy as np
- import cv2
- def compose(*funcs):
- """Compose arbitrarily many functions, evaluated left to right.
- Reference: https://mathieularose.com/function-composition-in-python/
- """
- if funcs:
- return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs)
- else:
- raise ValueError('Composition of empty sequence not supported.')
- def box_iou(b1, b2):
- """Return iou tensor
- Parameters
- ----------
- b1: tensor, shape=(i1,...,iN, 4), xywh
- b2: tensor, shape=(j, 4), xywh
- Returns
- -------
- iou: tensor, shape=(i1,...,iN, j)
- """
- # Expand dim to apply broadcasting.
- b1 = K.expand_dims(b1, -2)
- b1_xy = b1[..., :2]
- b1_wh = b1[..., 2:4]
- b1_wh_half = b1_wh/2.
- b1_mins = b1_xy - b1_wh_half
- b1_maxes = b1_xy + b1_wh_half
- # Expand dim to apply broadcasting.
- b2 = K.expand_dims(b2, 0)
- b2_xy = b2[..., :2]
- b2_wh = b2[..., 2:4]
- b2_wh_half = b2_wh/2.
- b2_mins = b2_xy - b2_wh_half
- b2_maxes = b2_xy + b2_wh_half
- intersect_mins = K.maximum(b1_mins, b2_mins)
- intersect_maxes = K.minimum(b1_maxes, b2_maxes)
- intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
- intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
- b1_area = b1_wh[..., 0] * b1_wh[..., 1]
- b2_area = b2_wh[..., 0] * b2_wh[..., 1]
- iou = intersect_area / (b1_area + b2_area - intersect_area)
- return iou
- def get_classes(classes_path):
- """loads the classes"""
- with open(classes_path) as f:
- class_names = f.readlines()
- class_names = [c.strip() for c in class_names]
- return class_names
- def get_anchors(anchors_path):
- """loads the anchors from a file"""
- with open(anchors_path) as f:
- anchors = f.readline()
- anchors = [float(x) for x in anchors.split(',')]
- return np.array(anchors).reshape(-1, 2)
- def get_colors(number, bright=True):
- """
- Generate random colors for drawing bounding boxes.
- To get visually distinct colors, generate them in HSV space then
- convert to RGB.
- """
- if number <= 0:
- return []
- brightness = 1.0 if bright else 0.7
- hsv_tuples = [(x / number, 1., brightness)
- for x in range(number)]
- colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
- colors = list(
- map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
- colors))
- np.random.seed(10101) # Fixed seed for consistent colors across runs.
- np.random.shuffle(colors) # Shuffle colors to decorrelate adjacent classes.
- np.random.seed(None) # Reset seed to default.
- return colors
- labelType = Enum('labelType', ('LABEL_TOP_OUTSIDE',
- 'LABEL_BOTTOM_OUTSIDE',
- 'LABEL_TOP_INSIDE',
- 'LABEL_BOTTOM_INSIDE',))
- def draw_boxes(image, out_boxes, out_classes, out_scores, class_names, colors):
- print()
- font = ImageFont.truetype(font='yolo_data/FiraMono-Medium.otf',
- size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
- thickness = (image.size[0] + image.size[1]) // 300
- box_list = []
- for i, c in reversed(list(enumerate(out_classes))):
- predicted_class = class_names[c]
- box = out_boxes[i]
- score = out_scores[i]
- label = '{} {:.2f}'.format(predicted_class, score)
- draw = ImageDraw.Draw(image)
- label_size = draw.textsize(label, font)
- top, left, bottom, right = box
- top = max(0, np.floor(top + 0.5).astype('int32'))
- left = max(0, np.floor(left + 0.5).astype('int32'))
- bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
- right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
- print(label, (left, top), (right, bottom))
- box_list.append([(left, top), (right, bottom)])
- if top - label_size[1] >= 0:
- text_origin = np.array([left, top - label_size[1]])
- else:
- text_origin = np.array([left, top + 1])
- # My kingdom for a good redistributable image drawing library.
- for i in range(thickness):
- draw.rectangle(
- [left + i, top + i, right - i, bottom - i],
- outline=colors[c])
- draw.rectangle(
- [tuple(text_origin), tuple(text_origin + label_size)],
- fill=colors[c])
- draw.text(text_origin, label, fill=(0, 0, 0), font=font)
- del draw
- return image, box_list
- def draw_label(image, text, color, coords, label_type=labelType.LABEL_TOP_OUTSIDE):
- font = cv2.FONT_HERSHEY_PLAIN
- font_scale = 1.
- (text_width, text_height) = cv2.getTextSize(text, font, fontScale=font_scale, thickness=1)[0]
- padding = 5
- rect_height = text_height + padding * 2
- rect_width = text_width + padding * 2
- (x, y) = coords
- if label_type == labelType.LABEL_TOP_OUTSIDE or label_type == labelType.LABEL_BOTTOM_INSIDE:
- cv2.rectangle(image, (x, y), (x + rect_width, y - rect_height), color, cv2.FILLED)
- cv2.putText(image, text, (x + padding, y - text_height + padding), font,
- fontScale=font_scale,
- color=(255, 255, 255),
- lineType=cv2.LINE_AA)
- else:
- # LABEL_BOTTOM_OUTSIDE or LABEL_TOP_INSIDE
- cv2.rectangle(image, (x, y), (x + rect_width, y + rect_height), color, cv2.FILLED)
- cv2.putText(image, text, (x + padding, y + text_height + padding), font,
- fontScale=font_scale,
- color=(255, 255, 255),
- lineType=cv2.LINE_AA)
- return image
- def pil_resize(image_np, height, width):
- image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
- image_pil = image_pil.resize((int(width), int(height)), Image.BICUBIC)
- image_np = cv2.cvtColor(np.asarray(image_pil), cv2.COLOR_RGB2BGR)
- return image_np
- def pil_resize_a(image_np, height, width):
- image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGRA2RGBA))
- image_pil = image_pil.resize((int(width), int(height)), Image.BICUBIC)
- image_np = cv2.cvtColor(np.asarray(image_pil), cv2.COLOR_RGBA2BGRA)
- return image_np
- def np2pil(image_np):
- image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
- return image_pil
- def pil2np(image_pil):
- image_np = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
- return image_np
- def np2pil_a(image_np):
- image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGRA2RGBA))
- return image_pil
- def pil2np_a(image_pil):
- image_np = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGBA2BGRA)
- return image_np
- def pil_rotate(image_np, angle, fill_color=(255, 255, 255)):
- image_pil = np2pil(image_np)
- image_rotate = image_pil.rotate(angle, expand=False, fillcolor=fill_color)
- image_np = pil2np(image_rotate)
- return image_np
- def pil_rotate_a(image_np, angle):
- image_pil = np2pil_a(image_np)
- image_rotate = image_pil.rotate(angle, expand=False, fillcolor=(255, 255, 255))
- image_np = pil2np_a(image_rotate)
- return image_np
|