parent
1d177be348
commit
2999367415
|
@ -0,0 +1,17 @@
|
|||
from .visual import Image, Template
|
||||
from .geometry import Point, Rect, Size, Vector2D, Vector3D, is_point, is_rect
|
||||
from .geometry import RectTuple, PointTuple
|
||||
|
||||
__all__ = [
|
||||
'Image',
|
||||
'Template',
|
||||
'Point',
|
||||
'Rect',
|
||||
'Size',
|
||||
'Vector2D',
|
||||
'Vector3D',
|
||||
'is_point',
|
||||
'is_rect',
|
||||
'RectTuple',
|
||||
'PointTuple'
|
||||
]
|
|
@ -0,0 +1,290 @@
|
|||
from typing import Generic, TypeVar, TypeGuard, overload
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class Vector2D(Generic[T]):
|
||||
"""2D 坐标类"""
|
||||
def __init__(self, x: T, y: T, *, name: str | None = None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.name: str | None = name
|
||||
"""坐标的名称。"""
|
||||
|
||||
def __getitem__(self, item: int):
|
||||
if item == 0:
|
||||
return self.x
|
||||
elif item == 1:
|
||||
return self.y
|
||||
else:
|
||||
raise IndexError
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Point<"{self.name}" at ({self.x}, {self.y})>'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'({self.x}, {self.y})'
|
||||
|
||||
|
||||
class Vector3D(Generic[T]):
|
||||
"""三元组类。"""
|
||||
def __init__(self, x: T, y: T, z: T, *, name: str | None = None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.name: str | None = name
|
||||
"""坐标的名称。"""
|
||||
|
||||
def __getitem__(self, item: int):
|
||||
if item == 0:
|
||||
return self.x
|
||||
elif item == 1:
|
||||
return self.y
|
||||
elif item == 2:
|
||||
return self.z
|
||||
else:
|
||||
raise IndexError
|
||||
|
||||
@property
|
||||
def xyz(self) -> tuple[T, T, T]:
|
||||
"""
|
||||
三元组 (x, y, z)。OpenCV 格式的坐标。
|
||||
"""
|
||||
return self.x, self.y, self.z
|
||||
|
||||
@property
|
||||
def xy(self) -> tuple[T, T]:
|
||||
"""
|
||||
二元组 (x, y)。OpenCV 格式的坐标。
|
||||
"""
|
||||
return self.x, self.y
|
||||
|
||||
class Vector4D(Generic[T]):
|
||||
"""四元组类。"""
|
||||
def __init__(self, x: T, y: T, z: T, w: T, *, name: str | None = None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.w = w
|
||||
self.name: str | None = name
|
||||
"""坐标的名称。"""
|
||||
|
||||
def __getitem__(self, item: int):
|
||||
if item == 0:
|
||||
return self.x
|
||||
elif item == 1:
|
||||
return self.y
|
||||
elif item == 2:
|
||||
return self.z
|
||||
elif item == 3:
|
||||
return self.w
|
||||
else:
|
||||
raise IndexError
|
||||
|
||||
Size = Vector2D[int]
|
||||
"""尺寸。相当于 Vector2D[int]"""
|
||||
RectTuple = tuple[int, int, int, int]
|
||||
"""矩形。(x, y, w, h)"""
|
||||
PointTuple = tuple[int, int]
|
||||
"""点。(x, y)"""
|
||||
|
||||
class Point(Vector2D[int]):
|
||||
"""点。"""
|
||||
|
||||
@property
|
||||
def xy(self) -> PointTuple:
|
||||
"""
|
||||
二元组 (x, y)。OpenCV 格式的坐标。
|
||||
"""
|
||||
return self.x, self.y
|
||||
|
||||
def offset(self, dx: int, dy: int) -> 'Point':
|
||||
"""
|
||||
偏移坐标。
|
||||
|
||||
:param dx: 偏移量。
|
||||
:param dy: 偏移量。
|
||||
:return: 偏移后的坐标。
|
||||
"""
|
||||
return Point(self.x + dx, self.y + dy, name=self.name)
|
||||
|
||||
def __add__(self, other: 'Point | PointTuple') -> 'Point':
|
||||
"""
|
||||
相加。
|
||||
|
||||
:param other: 另一个 Point 对象或二元组 (x: int, y: int)。
|
||||
:return: 相加后的点。
|
||||
"""
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x + other.x, self.y + other.y, name=self.name)
|
||||
else:
|
||||
return Point(self.x + other[0], self.y + other[1], name=self.name)
|
||||
|
||||
def __sub__(self, other: 'Point | PointTuple') -> 'Point':
|
||||
"""
|
||||
相减。
|
||||
|
||||
:param other: 另一个 Point 对象或二元组 (x: int, y: int)。
|
||||
:return: 相减后的点。
|
||||
"""
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x - other.x, self.y - other.y, name=self.name)
|
||||
else:
|
||||
return Point(self.x - other[0], self.y - other[1], name=self.name)
|
||||
|
||||
class Rect:
|
||||
"""
|
||||
矩形类。
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
x: int | None = None,
|
||||
y: int | None = None,
|
||||
w: int | None = None,
|
||||
h: int | None = None,
|
||||
*,
|
||||
xywh: RectTuple | None = None,
|
||||
name: str | None = None,
|
||||
):
|
||||
"""
|
||||
从给定的坐标信息创建矩形。
|
||||
|
||||
参数 `x`, `y`, `w`, `h` 和 `xywh` 必须至少指定一组。
|
||||
|
||||
:param x: 矩形左上角的 X 坐标。
|
||||
:param y: 矩形左上角的 Y 坐标。
|
||||
:param w: 矩形的宽度。
|
||||
:param h: 矩形的高度。
|
||||
:param xywh: 四元组 (x, y, w, h)。
|
||||
:param name: 矩形的名称。
|
||||
:raises ValueError: 提供的坐标参数不完整时抛出。
|
||||
"""
|
||||
if xywh is not None:
|
||||
x, y, w, h = xywh
|
||||
elif (
|
||||
x is not None and
|
||||
y is not None and
|
||||
w is not None and
|
||||
h is not None
|
||||
):
|
||||
pass
|
||||
else:
|
||||
raise ValueError('Either xywh or x, y, w, h must be provided.')
|
||||
|
||||
self.x1 = x
|
||||
"""矩形左上角的 X 坐标。"""
|
||||
self.y1 = y
|
||||
"""矩形左上角的 Y 坐标。"""
|
||||
self.w = w
|
||||
"""矩形的宽度。"""
|
||||
self.h = h
|
||||
"""矩形的高度。"""
|
||||
self.name: str | None = name
|
||||
"""矩形的名称。"""
|
||||
|
||||
@classmethod
|
||||
def from_xyxy(cls, x1: int, y1: int, x2: int, y2: int) -> 'Rect':
|
||||
"""
|
||||
从 (x1, y1, x2, y2) 创建矩形。
|
||||
:return: 创建结果。
|
||||
"""
|
||||
return cls(x1, y1, x2 - x1, y2 - y1)
|
||||
|
||||
@property
|
||||
def x2(self) -> int:
|
||||
"""矩形右下角的 X 坐标。"""
|
||||
return self.x1 + self.w
|
||||
|
||||
@x2.setter
|
||||
def x2(self, value: int):
|
||||
self.w = value - self.x1
|
||||
|
||||
@property
|
||||
def y2(self) -> int:
|
||||
"""矩形右下角的 Y 坐标。"""
|
||||
return self.y1 + self.h
|
||||
|
||||
@y2.setter
|
||||
def y2(self, value: int):
|
||||
self.h = value - self.y1
|
||||
|
||||
@property
|
||||
def xywh(self) -> RectTuple:
|
||||
"""
|
||||
四元组 (x1, y1, w, h)。OpenCV 格式的坐标。
|
||||
"""
|
||||
return self.x1, self.y1, self.w, self.h
|
||||
|
||||
@property
|
||||
def xyxy(self) -> RectTuple:
|
||||
"""
|
||||
四元组 (x1, y1, x2, y2)。
|
||||
"""
|
||||
return self.x1, self.y1, self.x2, self.y2
|
||||
|
||||
@property
|
||||
def top_left(self) -> Point:
|
||||
"""
|
||||
矩形的左上角点。
|
||||
"""
|
||||
if self.name:
|
||||
name = "Left-top of rect "+ self.name
|
||||
else:
|
||||
name = None
|
||||
return Point(self.x1, self.y1, name=name)
|
||||
|
||||
@property
|
||||
def bottom_right(self) -> Point:
|
||||
"""
|
||||
矩形的右下角点。
|
||||
"""
|
||||
if self.name:
|
||||
name = "Right-bottom of rect "+ self.name
|
||||
else:
|
||||
name = None
|
||||
return Point(self.x2, self.y2, name=name)
|
||||
|
||||
@property
|
||||
def left_bottom(self) -> Point:
|
||||
"""
|
||||
矩形的左下角点。
|
||||
"""
|
||||
if self.name:
|
||||
name = "Left-bottom of rect "+ self.name
|
||||
else:
|
||||
name = None
|
||||
return Point(self.x1, self.y2, name=name)
|
||||
|
||||
@property
|
||||
def right_top(self) -> Point:
|
||||
"""
|
||||
矩形的右上角点。
|
||||
"""
|
||||
if self.name:
|
||||
name = "Right-top of rect "+ self.name
|
||||
else:
|
||||
name = None
|
||||
return Point(self.x2, self.y1, name=name)
|
||||
|
||||
@property
|
||||
def center(self) -> Point:
|
||||
"""
|
||||
矩形的中心点。
|
||||
"""
|
||||
if self.name:
|
||||
name = "Center of rect "+ self.name
|
||||
else:
|
||||
name = None
|
||||
return Point(self.x1 + self.w // 2, self.y1 + self.h // 2, name=name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Rect<"{self.name}" at (x={self.x1}, y={self.y1}, w={self.w}, h={self.h})>'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'(x={self.x1}, y={self.y1}, w={self.w}, h={self.h})'
|
||||
|
||||
|
||||
def is_point(obj: object) -> TypeGuard[Point]:
|
||||
return isinstance(obj, Point)
|
||||
|
||||
def is_rect(obj: object) -> TypeGuard[Rect]:
|
||||
return isinstance(obj, Rect)
|
|
@ -0,0 +1,63 @@
|
|||
import logging
|
||||
|
||||
from cv2.typing import MatLike
|
||||
|
||||
from .geometry import Size
|
||||
from kotonebot.util import cv2_imread
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Image:
|
||||
"""
|
||||
图像类。
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
pixels: MatLike | None = None,
|
||||
file_path: str | None = None,
|
||||
lazy_load: bool = False,
|
||||
name: str | None = None,
|
||||
description: str | None = None
|
||||
):
|
||||
"""
|
||||
从内存数据或图像文件创建图像类。
|
||||
|
||||
:param pixels: 图像数据。格式必须为 BGR。
|
||||
:param file_path: 图像文件路径。
|
||||
:param lazy_load: 是否延迟加载图像数据。
|
||||
若为 False,立即载入,否则仅当访问图像数据时才载入。仅当从文件创建图像类时生效。
|
||||
:param name: 图像名称。
|
||||
:param description: 图像描述。
|
||||
"""
|
||||
self.name: str | None = name
|
||||
"""图像名称。"""
|
||||
self.description: str | None = description
|
||||
"""图像描述。"""
|
||||
self.file_path: str | None = file_path
|
||||
"""图像的文件路径。"""
|
||||
self.__pixels: MatLike | None = None
|
||||
# 立即加载
|
||||
if not lazy_load and self.file_path:
|
||||
_ = self.pixels
|
||||
# 传入像素数据而不是文件
|
||||
if pixels is not None:
|
||||
self.__pixels = pixels
|
||||
|
||||
@property
|
||||
def pixels(self) -> MatLike:
|
||||
"""图像的像素数据。"""
|
||||
if self.__pixels is None:
|
||||
if not self.file_path:
|
||||
raise ValueError('Either pixels or file_path must be provided.')
|
||||
logger.debug('Loading image "%s" from %s...', self.name or '(unnamed)', self.file_path)
|
||||
self.__pixels = cv2_imread(self.file_path)
|
||||
return self.__pixels
|
||||
|
||||
@property
|
||||
def size(self) -> Size:
|
||||
return Size(self.pixels.shape[1], self.pixels.shape[0])
|
||||
|
||||
class Template(Image):
|
||||
"""
|
||||
模板图像类。
|
||||
"""
|
Loading…
Reference in New Issue