cvtools.utils.boxes 源代码

# -*- coding:utf-8 -*-
# author   : gfjiangly
# time     : 2019/6/10 11:13
# e-mail   : jgf0719@foxmail.com
# software : PyCharm
import numpy as np
import cv2.cv2 as cv
from shapely.geometry import Polygon


[文档]def x1y1wh_to_x1y1x2y2(xywh): """Convert [x1 y1 w h] box format to [x1 y1 x2 y2] format. supported type: list, type and np.ndarray """ if isinstance(xywh, (list, tuple)): # Single box given as a list of coordinates assert len(xywh) == 4 x1, y1 = xywh[0], xywh[1] x2 = x1 + np.maximum(0., xywh[2] - 1.) y2 = y1 + np.maximum(0., xywh[3] - 1.) return [x1, y1, x2, y2] elif isinstance(xywh, np.ndarray): # Multiple boxes given as a 2D ndarray return np.hstack( (xywh[:, 0:2], xywh[:, 0:2] + np.maximum(0, xywh[:, 2:4] - 1)) ) else: raise TypeError('Argument xywh must be a list, tuple, or numpy array.')
[文档]def x1y1x2y2_to_x1y1wh(xyxy): """Convert [x1 y1 x2 y2] box format to [x1 y1 w h] format.""" if isinstance(xyxy, (list, tuple)): # Single box given as a list of coordinates assert len(xyxy) == 4 x1, y1 = xyxy[0], xyxy[1] w = xyxy[2] - x1 + 1 h = xyxy[3] - y1 + 1 return [x1, y1, w, h] elif isinstance(xyxy, np.ndarray): # Multiple boxes given as a 2D ndarray return np.hstack((xyxy[:, 0:2], xyxy[:, 2:4] - xyxy[:, 0:2] + 1)) else: raise TypeError('Argument xyxy must be a list, tuple, or numpy array.')
[文档]def x1y1wh_to_x1y1x2y2x3y3x4y4(xywh): """Convert [x1 y1 w h] box format to [x1 y1 x2 y2 x3 y3 x4 y4] format. supported type: list, type and np.ndarray """ if isinstance(xywh, (list, tuple)): # Single box given as a list of coordinates assert len(xywh) == 4 x1, y1 = xywh[0], xywh[1] x4 = x1 + np.maximum(0., xywh[2] - 1.) y4 = y1 + np.maximum(0., xywh[3] - 1.) return [x1, y1, x4, y1, x4, y4, x1, y4] elif isinstance(xywh, np.ndarray): # Multiple boxes given as a 2D ndarray x1y1 = xywh[:, 0:2] x4y4 = xywh[:, 0:2] + np.maximum(0, xywh[:, 2:4] - 1) return np.hstack( (x1y1, np.hstack((x4y4[..., 0], x1y1[..., 1])), x4y4, np.hstack((x1y1[..., 0], x4y4[..., 1]))) ) else: raise TypeError('Argument xywh must be a list, tuple, or numpy array.')
[文档]def polygon_to_x1y1wh(polygon): """求polygon的外接正矩形 Args: polygon (list or np.array): 多边形点集 Returns: tuple: x1y1wh形式矩形 """ if not isinstance(polygon, np.ndarray): polygon = np.array(polygon) polygon = polygon.reshape(-1, 2) x1y1wh = cv.boundingRect(polygon) return x1y1wh
[文档]def xywh_to_x1y1x2y2(xywh): """Convert [x y w h] box format to [x1 y1 x2 y2] format.""" if isinstance(xywh, (list, tuple)): # Single box given as a list of coordinates assert len(xywh) == 4 x1, y1 = xywh[0] - xywh[2] / 2, xywh[1] - xywh[3] / 2 x2, y2 = xywh[0] + xywh[2] / 2, xywh[1] + xywh[3] / 2 return x1, y1, x2, y2 elif isinstance(xywh, np.ndarray): # Multiple boxes given as a 2D ndarray return np.hstack( (xywh[:, 0:2] - xywh[:, 2:4] / 2, xywh[:, 0:2] + xywh[:, 2:4] / 2) ) else: raise TypeError('Argument xywh must be a list, tuple, or numpy array.')
[文档]def x1y1x2y2_to_xywh(x1y1x2y2): """Convert [x1 y1 x2 y2] box format to [x y w h] format.""" if isinstance(x1y1x2y2, (list, tuple)): # Single box given as a list of coordinates assert len(x1y1x2y2) == 4 ct_x, ct_y = (x1y1x2y2[3] + x1y1x2y2[1]) / 2, (x1y1x2y2[2] + x1y1x2y2[0]) / 2 w, h = x1y1x2y2[3] - x1y1x2y2[1] + 1, x1y1x2y2[2] - x1y1x2y2[0] + 1 return ct_x, ct_y, w, h elif isinstance(x1y1x2y2, np.ndarray): # Multiple boxes given as a 2D ndarray return np.hstack( ((x1y1x2y2[:, 0:2] + x1y1x2y2[:, 2:4]) / 2, x1y1x2y2[:, 2:4] - x1y1x2y2[:, 0:2] + 1) ) else: raise TypeError('Argument x1y1x2y2 must be a list, tuple, or numpy array.')
[文档]def x1y1wh_to_xywh(x1y1wh): """Convert [x1 y1 w h] box format to [x y w h] format. supported type: list, type and np.ndarray """ if isinstance(x1y1wh, (list, tuple)): # Single box given as a list of coordinates assert len(x1y1wh) == 4 w = x1y1wh[2] h = x1y1wh[3] x, y = x1y1wh[0] + w/2, x1y1wh[1] + h/2 return x, y, w, h elif isinstance(x1y1wh, np.ndarray): # Multiple boxes given as a 2D ndarray return np.hstack( (x1y1wh[:, 0:2]+x1y1wh[:, 2:4]/2, x1y1wh[:, 2:4]) ) else: raise TypeError('Argument x1y1wh must be a list, tuple, or numpy array.')
# 已测试
[文档]def rotate_rect(rect, center, angle): """一个数学问题:2x2矩阵(坐标)与旋转矩阵相乘. 在笛卡尔坐标系中,angle>0, 逆时针旋转; angle<0, 顺时针旋转 Args: rect: x1y1x2y2形式矩形 center: 旋转中心点 angle: 旋转角度,范围在(-180, 180) Returns: x1y1x2y2x3y3x4y4 format box """ assert 180 > angle > -180 # 顺时针排列坐标 x1, y1, x3, y3 = rect x2, y2, x4, y4 = x3, y1, x1, y3 x, y = center # anti-clockwise to clockwise arc cosA = np.cos(np.pi / 180. * angle) sinA = np.sin(np.pi / 180. * angle) x1n = (x1 - x) * cosA - (y1 - y) * sinA + x y1n = (x1 - x) * sinA + (y1 - y) * cosA + y x2n = (x2 - x) * cosA - (y2 - y) * sinA + x y2n = (x2 - x) * sinA + (y2 - y) * cosA + y x3n = (x3 - x) * cosA - (y3 - y) * sinA + x y3n = (x3 - x) * sinA + (y3 - y) * cosA + y x4n = (x4 - x) * cosA - (y4 - y) * sinA + x y4n = (x4 - x) * sinA + (y4 - y) * cosA + y return [x1n, y1n, x2n, y2n, x3n, y3n, x4n, y4n] # 顺时针方向
[文档]def rotate_rects(rects, centers, angle): """一个数学问题:坐标矩阵与旋转矩阵相乘. 在笛卡尔坐标系中,angle>0, 逆时针旋转; angle<0, 顺时针旋转 return: x1y1x2y2x3y3x4y4 format box Args: rects: x1y1x2y2形式list或array centers: 旋转中心 angle: 旋转角度 """ if not isinstance(rects, np.ndarray): rects = np.array([rects]) if not isinstance(centers, np.ndarray): centers = np.array([centers]) assert len(centers) == len(rects) > 0 # assert 180 > angle > -180 # 顺时针排列坐标 rects = np.hstack((rects[:, :2], rects[:, 2:3], rects[:, 1:2], rects[:, 2:4], rects[:, 0:1], rects[:, 3:4])) coors = rects.reshape(-1, 2) cosA = np.tile(np.cos(np.pi / 180. * angle), (4, 1)).reshape(-1, 1) sinA = np.tile(np.sin(np.pi / 180. * angle), (4, 1)).reshape(-1, 1) x, y = coors[:, 0:1], coors[:, 1:2] x0, y0 = np.tile(centers[:, 0:1], (4, 1)), np.tile(centers[:, 1:2], (4, 1)) xn = (x - x0) * cosA - (y - y0) * sinA + x0 yn = (x - x0) * sinA + (y - y0) * cosA + y0 return np.hstack((xn, yn)).reshape(-1, 8) # 顺时针方向
[文档]def xywha_to_x1y1x2y2x3y3x4y4(xywha): """用旋转的思路做变换是最通用和最简单的 警告:目前多维一起操作还有些问题! Args: xywha: (5,)一维list或(K, 5)多维array """ if isinstance(xywha, (list, tuple)): assert len(xywha) == 5 return rotate_rect(xywha[:4], xywha[:2], xywha[4:5]) elif isinstance(xywha, np.ndarray): return rotate_rects(xywha[:, :4], xywha[:, :2], xywha[:, 4:5]) else: raise TypeError('Argument xywha must be a list, tuple, or numpy array.')
[文档]def tran_xywha_to_rbox(xywha): """包装opencv的cv.boxPoints函数 Args: xywha: ((xy),(wh),angle)形式tuple或list, xy为中心点 """ if len(xywha) != 3: xywha = (xywha[:2], xywha[2:4], xywha[4]) return cv.boxPoints(xywha) # 返回4*2数组
[文档]def get_min_area_rect(cnt): """包装cv.minAreaRect Args: cnt: (np.ndarray): [x1, y1, x2, y2, x3, y3, x4, y4, ...] Returns: xywha:((x,y), (w,h), a), (x,y) is Oriented Bounding Box(OBB)'s center point, a is orientation, which range is [-90, 0) """ assert isinstance(cnt, np.ndarray) cnt = cnt.reshape(-1, 2) # the clockwise output convex hull in the Cartesian coordinate system cnt_hull = cv.convexHull(cnt.astype(np.float32), clockwise=False) xywha = cv.minAreaRect(cnt_hull) return xywha
[文档]def trans_polygon_to_rbox(polygon): """求polygon的最小外接旋转矩形 Args: polygon: 一维数组,或(K, 2)数组 """ if not isinstance(polygon, np.ndarray): polygon = np.array(polygon) polygon = polygon.reshape(-1, 2).astype(np.float32) segm_hull = cv.convexHull(polygon, clockwise=False) xywha = cv.minAreaRect(segm_hull) rbox = cv.boxPoints(xywha).reshape(-1).tolist() return rbox
[文档]def cut_polygon(polygon, box): """求polygon与矩形框的交点,返回交点与外接矩形 Args: polygon (list or np.array): 多边形,支持一维list或(K,2)list/array 不必保持点的顺序 box (list or np.array): 必须是4个点形式list或array Returns: tuple: 第一个元素是交点, (K,2)数组;第二元素是交点的外接矩形,(4,)数组 """ # polygon可以不按点的顺序构建,但是必须使用convex_hull求交 if not isinstance(polygon, np.ndarray): polygon = np.array(polygon) polygon = polygon.reshape(-1, 2) polygon = Polygon(polygon).convex_hull box = Polygon(box).convex_hull # boundary属性对应的LineString可以直接转为array对象 # 最后一个点与第一个点相同,予以去除 inters = polygon.intersection(box) try: intersections = np.array(inters.boundary)[:-1] bounds = np.array(inters.bounds) # 外接矩形 except ValueError: return None, None return intersections, bounds