from __future__ import absolute_import, division import numpy as np from shapely.geometry import box, Polygon def center_error(rects1, rects2): r"""Center error. Args: rects1 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). rects2 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). """ centers1 = rects1[..., :2] + (rects1[..., 2:] - 1) / 2 centers2 = rects2[..., :2] + (rects2[..., 2:] - 1) / 2 errors = np.sqrt(np.sum(np.power(centers1 - centers2, 2), axis=-1)) return errors def normalized_center_error(rects1, rects2): r"""Center error normalized by the size of ground truth. Args: rects1 (numpy.ndarray): prediction box. An N x 4 numpy array, each line represent a rectangle (left, top, width, height). rects2 (numpy.ndarray): groudn truth box. An N x 4 numpy array, each line represent a rectangle (left, top, width, height). """ centers1 = rects1[..., :2] + (rects1[..., 2:] - 1) / 2 centers2 = rects2[..., :2] + (rects2[..., 2:] - 1) / 2 errors = np.sqrt(np.sum(np.power((centers1 - centers2)/np.maximum(np.array([[1.,1.]]), rects2[:, 2:]), 2), axis=-1)) return errors def rect_iou(rects1, rects2, bound=None): r"""Intersection over union. Args: rects1 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). rects2 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). bound (numpy.ndarray): A 4 dimensional array, denotes the bound (min_left, min_top, max_width, max_height) for ``rects1`` and ``rects2``. """ assert rects1.shape == rects2.shape if bound is not None: # bounded rects1 rects1[:, 0] = np.clip(rects1[:, 0], 0, bound[0]) rects1[:, 1] = np.clip(rects1[:, 1], 0, bound[1]) rects1[:, 2] = np.clip(rects1[:, 2], 0, bound[0] - rects1[:, 0]) rects1[:, 3] = np.clip(rects1[:, 3], 0, bound[1] - rects1[:, 1]) # bounded rects2 rects2[:, 0] = np.clip(rects2[:, 0], 0, bound[0]) rects2[:, 1] = np.clip(rects2[:, 1], 0, bound[1]) rects2[:, 2] = np.clip(rects2[:, 2], 0, bound[0] - rects2[:, 0]) rects2[:, 3] = np.clip(rects2[:, 3], 0, bound[1] - rects2[:, 1]) rects_inter = _intersection(rects1, rects2) areas_inter = np.prod(rects_inter[..., 2:], axis=-1) areas1 = np.prod(rects1[..., 2:], axis=-1) areas2 = np.prod(rects2[..., 2:], axis=-1) areas_union = areas1 + areas2 - areas_inter eps = np.finfo(float).eps ious = areas_inter / (areas_union + eps) ious = np.clip(ious, 0.0, 1.0) return ious def _intersection(rects1, rects2): r"""Rectangle intersection. Args: rects1 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). rects2 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height). """ assert rects1.shape == rects2.shape x1 = np.maximum(rects1[..., 0], rects2[..., 0]) y1 = np.maximum(rects1[..., 1], rects2[..., 1]) x2 = np.minimum(rects1[..., 0] + rects1[..., 2], rects2[..., 0] + rects2[..., 2]) y2 = np.minimum(rects1[..., 1] + rects1[..., 3], rects2[..., 1] + rects2[..., 3]) w = np.maximum(x2 - x1, 0) h = np.maximum(y2 - y1, 0) return np.stack([x1, y1, w, h]).T def poly_iou(polys1, polys2, bound=None): r"""Intersection over union of polygons. Args: polys1 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height); or an N x 8 numpy array, each line represent the coordinates (x1, y1, x2, y2, x3, y3, x4, y4) of 4 corners. polys2 (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height); or an N x 8 numpy array, each line represent the coordinates (x1, y1, x2, y2, x3, y3, x4, y4) of 4 corners. bound (numpy.ndarray, optional): A 2 dimensional array, denotes the image bound (width, height) for ``rects1`` and ``rects2``. """ assert polys1.ndim in [1, 2] if polys1.ndim == 1: polys1 = np.array([polys1]) polys2 = np.array([polys2]) assert len(polys1) == len(polys2) polys1 = _to_polygon(polys1) polys2 = _to_polygon(polys2) if bound is not None: bound = box(0, 0, bound[0], bound[1]) polys1 = [p.intersection(bound) for p in polys1] polys2 = [p.intersection(bound) for p in polys2] eps = np.finfo(float).eps ious = [] for poly1, poly2 in zip(polys1, polys2): area_inter = poly1.intersection(poly2).area area_union = poly1.union(poly2).area ious.append(area_inter / (area_union + eps)) ious = np.clip(ious, 0.0, 1.0) return ious def _to_polygon(polys): r"""Convert 4 or 8 dimensional array to Polygons Args: polys (numpy.ndarray): An N x 4 numpy array, each line represent a rectangle (left, top, width, height); or an N x 8 numpy array, each line represent the coordinates (x1, y1, x2, y2, x3, y3, x4, y4) of 4 corners. """ def to_polygon(x): assert len(x) in [4, 8] if len(x) == 4: return box(x[0], x[1], x[0] + x[2], x[1] + x[3]) elif len(x) == 8: return Polygon([(x[2 * i], x[2 * i + 1]) for i in range(4)]) if polys.ndim == 1: return to_polygon(polys) else: return [to_polygon(t) for t in polys]