instance.py
ultralytics\utils\instance.py
目录
instance.py
1.所需的库和模块
2.def _ntuple(n):
3.class Bboxes:
4.class Instances:
1.所需的库和模块
# Ultralytics YOLO 🚀, AGPL-3.0 licensefrom collections import abc
from itertools import repeat
from numbers import Number
from typing import Listimport numpy as npfrom .ops import ltwh2xywh, ltwh2xyxy, xywh2ltwh, xywh2xyxy, xyxy2ltwh, xyxy2xywh
2.def _ntuple(n):
# 这段代码定义了一个名为 _ntuple 的函数,它是一个高阶函数,用于创建一个新的函数 parse 。
# parse 函数接受一个参数 x ,并将其转换为一个长度为 n 的元组。如果 x 已经是一个可迭代对象( Iterable ),则直接返回这个对象;否则, parse 函数会重复 x 值 n 次,形成一个长度为 n 的元组。
# 这是一个接受一个整数参数 1.n 的函数。
def _ntuple(n):# 来自 PyTorch 内部。"""From PyTorch internals."""# _ntuple 函数内部定义了一个名为 parse 的嵌套函数,这个函数接受一个参数 1.x 。def parse(x):# 解析 XYWH 和 LTWH 之间的边界框格式。"""Parse bounding boxes format between XYWH and LTWH."""# 在Python中, abc.Iterable 是 collections.abc 模块中的一个抽象基类(Abstract Base Class, ABC),用于表示一个对象是可迭代的。一个对象被认为是可迭代的,如果它有一个 __iter__() 方法,该方法返回一个迭代器。迭代器是一个可以记住遍历的位置的对象,它有两个方法: __iter__() 和 __next__() 。# abc.Iterable 通常用于类型注解和类型检查,以确保函数或方法的参数是可迭代的。# parse 函数的逻辑是检查 x 是否是一个可迭代对象( Iterable )。如果是,就直接返回 x ;如果不是,就使用 itertools.repeat 函数重复 x 值 n 次,并将其转换为一个元组。return x if isinstance(x, abc.Iterable) else tuple(repeat(x, n))# _ntuple 函数返回 parse 函数。return parse
# 这个函数的一个典型用途是确保函数参数总是以元组的形式传递,无论用户输入的是单个值还是一个列表。# _ntuple 函数被用来创建两个新的函数 to_2tuple 和 to_4tuple ,这两个函数分别将输入转换为长度为2和长度为4的元组。
# _ntuple(2) 创建了一个名为 to_2tuple 的函数,它将任何输入转换为长度为2的元组。
to_2tuple = _ntuple(2)
# _ntuple(4) 创建了一个名为 to_4tuple 的函数,它将任何输入转换为长度为4的元组。
to_4tuple = _ntuple(4)# `xyxy` means left top and right bottom
# `xywh` means center x, center y and width, height(YOLO format)
# `ltwh` means left top and width, height(COCO format)
_formats = ["xyxy", "xywh", "ltwh"]__all__ = ("Bboxes",) # tuple or list
3.class Bboxes:
# 这段代码定义了一个名为 Bboxes 的类,它用于处理和存储边界框(bounding boxes)数据。边界框通常用于计算机视觉任务中,如目标检测,用来表示图像中目标的位置和大小。
class Bboxes:# 用于处理边界框的类。# 该类支持各种边界框格式,如“xyxy”、“xywh”和“ltwh”。边界框数据应以 numpy 数组的形式提供。# 注意:# 此类不处理边界框的规范化或非规范化。"""A class for handling bounding boxes.The class supports various bounding box formats like 'xyxy', 'xywh', and 'ltwh'.Bounding box data should be provided in numpy arrays.Attributes:bboxes (numpy.ndarray): The bounding boxes stored in a 2D numpy array.format (str): The format of the bounding boxes ('xyxy', 'xywh', or 'ltwh').Note:This class does not handle normalization or denormalization of bounding boxes."""# 这是类的构造函数,用于初始化 Bboxes 实例。 它接受两个参数: bboxes 和 format 。# 1.bboxes :是边界框数据,可以是一个一维数组(表示单个边界框)或二维数组(表示多个边界框)。# 2.format :是边界框数据的格式,默认为 "xyxy" ,表示边界框的坐标格式为 (x1, y1, x2, y2),其中 (x1, y1) 是左上角坐标,(x2, y2) 是右下角坐标。def __init__(self, bboxes, format="xyxy") -> None:# 使用指定格式的边界框数据初始化 Bboxes 类。"""Initializes the Bboxes class with bounding box data in a specified format."""# 这行代码检查 format 参数是否有效。 _formats 是一个包含所有支持的边界框格式的列表或元组。如果 format 不在 _formats 中,将抛出一个 AssertionError 。assert format in _formats, f"Invalid bounding box format: {format}, format must be one of {_formats}" # 边界框格式无效:{format},格式必须是 {_formats} 之一。# 这行代码确保 bboxes 参数是一个二维数组。如果 bboxes 是一维的(即 ndim == 1 ),则通过添加一个新的轴( None )将其转换为二维数组。bboxes = bboxes[None, :] if bboxes.ndim == 1 else bboxes# 这两行代码验证 bboxes 数组的维度和形状。确保 bboxes 是一个二维数组,且每个边界框有4个坐标值。assert bboxes.ndim == 2assert bboxes.shape[1] == 4# 这两行代码将传入的 bboxes 和 format 保存为实例变量,以便在类的其他方法中使用。self.bboxes = bboxesself.format = format# 这行代码被注释掉了,但它暗示了可能有一个 normalized 参数,用于表示边界框坐标是否被归一化。这个参数在当前代码中没有使用,但可以在未来扩展功能时考虑。# self.normalized = normalized# 这个类可以用于存储和处理边界框数据,确保数据的一致性和正确性。例如,你可以创建一个 Bboxes 实例,然后使用它来处理图像中的目标检测结果。# 这段代码是 Bboxes 类的一个方法,名为 convert ,它用于将边界框的格式从一种类型转换为另一种类型。# 1.self : 这是一个指向类实例本身的参数,用于访问类的属性和方法。在类的实例方法中, self 总是方法的第一个参数,它允许我们访问实例的属性和方法。# 2.format : 这是一个参数,表示目标边界框格式。它应该是一个字符串,指定了边界框数据需要转换成的格式。例如,它可以是 "xyxy" 、 "xywh" 或 "ltwh" ,这些格式分别代表不同的坐标表示方式。def convert(self, format):# 将边界框格式从一种类型转换为另一种类型。"""Converts bounding box format from one type to another."""# 这行代码检查传入的 format 参数是否是支持的边界框格式之一。 _formats 是一个包含所有支持的边界框格式的列表或元组。如果 format 不在 _formats 中,将抛出一个 AssertionError 。assert format in _formats, f"Invalid bounding box format: {format}, format must be one of {_formats}" # 边界框格式无效:{format},格式必须是 {_formats} 之一。# 如果当前边界框的格式 ( self.format ) 与目标格式 ( format ) 相同,则不需要转换,直接返回。if self.format == format:return# 如果当前格式是 "xyxy" ,则根据目标格式选择相应的转换函数。 "xyxy" 格式表示边界框的坐标为 (x1, y1, x2, y2)。elif self.format == "xyxy":# 如果目标格式是 "xywh" ,则使用 xyxy2xywh 函数进行转换。 "xywh" 格式表示边界框的坐标为 (x, y, w, h),其中 (x, y) 是左上角坐标,w 和 h 分别是宽度和高度。# 如果目标格式是 "ltwh" ,则使用 xyxy2ltwh 函数进行转换。 "ltwh" 格式表示边界框的坐标为 (left, top, width, height)。func = xyxy2xywh if format == "xywh" else xyxy2ltwh# 如果当前格式是 "xywh" ,则根据目标格式选择相应的转换函数。elif self.format == "xywh":# 如果目标格式是 "xyxy" ,则使用 xywh2xyxy 函数进行转换。 如果目标格式是 "ltwh" ,则使用 xywh2ltwh 函数进行转换。func = xywh2xyxy if format == "xyxy" else xywh2ltwh# 如果当前格式是 "ltwh" ,则根据目标格式选择相应的转换函数。else:# 如果目标格式是 "xyxy" ,则使用 ltwh2xyxy 函数进行转换。 如果目标格式是 "xywh" ,则使用 ltwh2xywh 函数进行转换。func = ltwh2xyxy if format == "xyxy" else ltwh2xywh# 调用选择的转换函数,并将结果赋值给 self.bboxes ,从而更新边界框数据。self.bboxes = func(self.bboxes)# 更新 self.format 为新的目标格式。self.format = format# 这个方法允许 Bboxes 类的实例在不同的边界框格式之间进行转换。# 这段代码定义了 Bboxes 类中的 areas 方法,用于计算边界框的面积。这个方法根据边界框的格式不同,计算面积的方式也不同。# 这是 areas 方法的定义,它是一个实例方法,不接受除了 1.self 之外的任何参数。def areas(self):"""Return box areas."""# 这是计算面积的表达式,它根据 self.format 的值选择不同的计算方式。# 如果 self.format 是 "xyxy" ,则边界框的格式是 (x1, y1, x2, y2),面积计算为 (x2 - x1) * (y2 - y1) 。# 如果 self.format 不是 "xyxy" ,则假定边界框的格式是 "xywh" 或 "ltwh" ,这两种格式都表示为 (x, y, w, h) 或 (left, top, width, height),面积计算为 w * h 。return ((self.bboxes[:, 2] - self.bboxes[:, 0]) * (self.bboxes[:, 3] - self.bboxes[:, 1]) # format xyxyif self.format == "xyxy"else self.bboxes[:, 3] * self.bboxes[:, 2] # format xywh or ltwh)# 这个方法利用了 NumPy 数组的广播机制,可以直接对数组的每一行进行操作,计算出所有边界框的面积。返回的是一个一维数组,包含了每个边界框的面积。# def denormalize(self, w, h):# if not self.normalized:# return# assert (self.bboxes <= 1.0).all()# self.bboxes[:, 0::2] *= w# self.bboxes[:, 1::2] *= h# self.normalized = False## def normalize(self, w, h):# if self.normalized:# return# assert (self.bboxes > 1.0).any()# self.bboxes[:, 0::2] /= w# self.bboxes[:, 1::2] /= h# self.normalized = True# 这段代码定义了 Bboxes 类中的 mul 方法,用于将边界框的坐标按给定的比例进行缩放。# 这是 mul 方法的定义,它是一个实例方法,接受一个参数。# 1.scale :用于指定缩放比例。def mul(self, scale):# 将边界框坐标乘以比例因子。"""Multiply bounding box coordinates by scale factor(s).Args:scale (int | tuple | list): Scale factor(s) for four coordinates.If int, the same scale is applied to all coordinates."""# 这行代码检查 scale 是否是一个数字( Number ),如果是,则使用 to_4tuple 函数将单个数字转换为长度为4的元组,每个元素都是这个数字。 Number 是一个抽象基类,包括了所有的数字类型,如 int 、 float 等。if isinstance(scale, Number):scale = to_4tuple(scale)# 这行代码确保 scale 是一个元组或列表类型,如果不是,则抛出 AssertionError 。assert isinstance(scale, (tuple, list))# 这行代码确保 scale 的长度为4,如果不是,则抛出 AssertionError 。这意味着 scale 应该是一个包含四个缩放比例的元组或列表,分别对应边界框的 x1, y1, x2, y2 坐标。assert len(scale) == 4# 这行代码将边界框的 x1 坐标乘以 scale 元组的第一个元素,实现 x 方向的缩放。self.bboxes[:, 0] *= scale[0]# 这行代码将边界框的 y1 坐标乘以 scale 元组的第二个元素,实现 y 方向的缩放。self.bboxes[:, 1] *= scale[1]# 这行代码将边界框的 x2 坐标乘以 scale 元组的第三个元素,实现 x 方向的缩放。self.bboxes[:, 2] *= scale[2]# 这行代码将边界框的 y2 坐标乘以 scale 元组的第四个元素,实现 y 方向的缩放。self.bboxes[:, 3] *= scale[3]# 这个方法会直接修改 self.bboxes 数组中的坐标值,实现边界框的缩放。# 这段代码定义了 Bboxes 类中的 add 方法,用于将边界框的坐标按给定的偏移量进行平移。# 这是 add 方法的定义,它是一个实例方法,接受一个参数。# 1.offset :用于指定平移的偏移量。def add(self, offset):# 为边界框坐标添加偏移量。"""Add offset to bounding box coordinates.Args:offset (int | tuple | list): Offset(s) for four coordinates.If int, the same offset is applied to all coordinates."""# 这行代码检查 offset 是否是一个数字( Number ),如果是,则使用 to_4tuple 函数将单个数字转换为长度为4的元组,每个元素都是这个数字。 Number 是一个抽象基类,包括了所有的数字类型,如 int 、 float 等。if isinstance(offset, Number):offset = to_4tuple(offset)# 这行代码确保 offset 是一个元组或列表类型,如果不是,则抛出 AssertionError 。assert isinstance(offset, (tuple, list))# 这行代码确保 offset 的长度为4,如果不是,则抛出 AssertionError 。这意味着 offset 应该是一个包含四个偏移量的元组或列表,分别对应边界框的 x1, y1, x2, y2 坐标。assert len(offset) == 4# 这行代码将边界框的 x1 坐标增加 offset 元组的第一个元素,实现 x 方向的平移。self.bboxes[:, 0] += offset[0]# 这行代码将边界框的 y1 坐标增加 offset 元组的第二个元素,实现 y 方向的平移。self.bboxes[:, 1] += offset[1]# 这行代码将边界框的 x2 坐标增加 offset 元组的第三个元素,实现 x 方向的平移。self.bboxes[:, 2] += offset[2]# 这行代码将边界框的 y2 坐标增加 offset 元组的第四个元素,实现 y 方向的平移。self.bboxes[:, 3] += offset[3]# 这个方法会直接修改 self.bboxes 数组中的坐标值,实现边界框的平移。# 这段代码定义了 Bboxes 类中的 __len__ 方法,这是一个特殊方法,用于返回对象的长度,即边界框的数量。# 这是 __len__ 方法的定义,它是一个实例方法,不接受除了 1.self 之外的任何参数。def __len__(self):# 返回边界框的数量。"""Return the number of boxes."""# 这行代码返回 self.bboxes 数组的长度,即边界框的数量。 len 函数用于获取数组中的元素个数。return len(self.bboxes)# 这个方法使得 Bboxes 类的实例可以像列表或其他集合类型一样使用内置的 len() 函数来获取边界框的数量。这是一个很方便的特性,因为它允许开发者以一种直观的方式来获取边界框的数量。# 这段代码定义了 Bboxes 类的 concatenate 静态方法,用于将多个 Bboxes 实例的边界框数据进行拼接。# 在Python中, @classmethod 是一个装饰器,用于将一个普通的方法转换为类方法。类方法的第一个参数总是 cls ,它代表类本身,而不是类的实例。这意味着你可以在不创建类实例的情况下调用这个方法,并且可以在这个方法内部访问类的属性和方法。# 类方法通常用于不需要类实例就可以执行的操作,例如 :# 创建类实例时不依赖于类的状态。# 需要访问类属性而不是实例属性。# 实现备选构造器(alternative constructors)。# 类方法可以被子类继承,并且可以被子类的实例和子类本身调用。# 这个装饰器表示 concatenate 是一个类方法,它的第一个参数是 cls ,代表类本身,而不是类的实例。 @classmethod 允许这个方法作为类方法被调用,而不需要一个 Bboxes 类的实例。这个方法使用 cls 参数来创建一个新的 Bboxes 实例,这是类方法的一个典型用例。@classmethod# 这是 concatenate 方法的定义,它接受两个参数。# cls :类本身。# 1.boxes_list :一个包含多个 Bboxes 实例的列表或元组。# 2.axis :指定拼接的轴,默认为0。# 方法的返回类型是 Bboxes ,表示返回的将是 Bboxes 类的一个实例。def concatenate(cls, boxes_list: List["Bboxes"], axis=0) -> "Bboxes":# 将 Bboxes 对象列表连接成单个 Bboxes 对象。# 注意:# 输入应为 Bboxes 对象的列表或元组。"""Concatenate a list of Bboxes objects into a single Bboxes object.Args:boxes_list (List[Bboxes]): A list of Bboxes objects to concatenate.axis (int, optional): The axis along which to concatenate the bounding boxes.Defaults to 0.Returns:Bboxes: A new Bboxes object containing the concatenated bounding boxes.Note:The input should be a list or tuple of Bboxes objects."""# 这行代码确保 boxes_list 是一个列表或元组。assert isinstance(boxes_list, (list, tuple))# 如果 boxes_list 为空,则返回一个空的 Bboxes 实例。if not boxes_list:return cls(np.empty(0))# all() # 在Python中, all() 是一个内置函数,用于检查可迭代对象(如列表、元组、集合等)中的所有元素是否都为 True 。如果所有元素都为 True (或者可迭代对象为空),则 all() 函数返回 True ;否则,返回 False 。# 这行代码确保 boxes_list 中的所有元素都是 Bboxes 实例。assert all(isinstance(box, Bboxes) for box in boxes_list)# 如果 boxes_list 中只有一个 Bboxes 实例,则直接返回该实例。if len(boxes_list) == 1:return boxes_list[0]# 对于 boxes_list 中的每个 Bboxes 实例,提取其 bboxes 属性,并使用 np.concatenate 函数沿着指定的 axis 轴进行拼接。 然后使用 cls 创建一个新的 Bboxes 实例,并返回。return cls(np.concatenate([b.bboxes for b in boxes_list], axis=axis))# 这个方法允许开发者轻松地将多个 Bboxes 实例的边界框数据合并为一个 Bboxes 实例,这在处理多个图像或多个检测结果时非常有用。# 这段代码定义了 Bboxes 类中的 __getitem__ 方法,这是一个特殊方法,用于实现对类实例的索引操作。# 这是 __getitem__ 方法的定义,它是一个实例方法,接受两个参数。# 1.self :类的实例。# 2.index :用于索引的值,可以是整数、切片对象或其它类型的索引。# 方法的返回类型是 Bboxes ,表示返回的将是 Bboxes 类的一个实例。def __getitem__(self, index) -> "Bboxes":# 使用索引检索特定边界框或一组边界框。# 注意:# 使用布尔索引时,请确保提供长度与边界框数量相同的布尔数组。"""Retrieve a specific bounding box or a set of bounding boxes using indexing.Args:index (int, slice, or np.ndarray): The index, slice, or boolean array to selectthe desired bounding boxes.Returns:Bboxes: A new Bboxes object containing the selected bounding boxes.Raises:AssertionError: If the indexed bounding boxes do not form a 2-dimensional matrix.Note:When using boolean indexing, make sure to provide a boolean array with the samelength as the number of bounding boxes."""# 这行代码检查 index 是否是一个整数。if isinstance(index, int):# 如果 index 是整数,这个方法将返回一个新的 Bboxes 实例,其中只包含原始 self.bboxes 数组中由 index 指定的单个边界框。 view(1, -1) 确保返回的是一个二维数组,即使只选择了一个边界框。return Bboxes(self.bboxes[index].view(1, -1))# 如果 index 不是整数,这个方法将使用 index 对 self.bboxes 进行索引操作,并将结果存储在变量 b 中。b = self.bboxes[index]# 这行代码断言索引操作的结果 b 必须是一个二维数组。如果不是二维数组,将抛出 AssertionError ,并显示错误信息。assert b.ndim == 2, f"Indexing on Bboxes with {index} failed to return a matrix!" # 使用 {index} 对 Bboxes 进行索引无法返回矩阵!# 最后,返回一个新的 Bboxes 实例,包含索引操作的结果。return Bboxes(b)# 这个方法使得 Bboxes 类的实例可以使用索引操作符 [] 来访问单个或多个边界框,并返回一个新的 Bboxes 实例。这对于处理单个边界框或边界框子集非常有用。
4.class Instances:
# 这段代码定义了一个名为 Instances 的 Python 类,它用于表示图像中的实例,比如目标检测中的目标实例。
# 类定义。这行代码定义了一个名为 Instances 的类。
class Instances:# 用于图像中检测到的对象的边界框、片段和关键点的容器。# 注意:# 边界框格式为“xywh”或“xyxy”,由 `bbox_format` 参数确定。# 此类不执行输入验证,并假定输入格式正确。"""Container for bounding boxes, segments, and keypoints of detected objects in an image.Attributes:_bboxes (Bboxes): Internal object for handling bounding box operations.keypoints (ndarray): keypoints(x, y, visible) with shape [N, 17, 3]. Default is None.normalized (bool): Flag indicating whether the bounding box coordinates are normalized.segments (ndarray): Segments array with shape [N, 1000, 2] after resampling.Args:bboxes (ndarray): An array of bounding boxes with shape [N, 4].segments (list | ndarray, optional): A list or array of object segments. Default is None.keypoints (ndarray, optional): An array of keypoints with shape [N, 17, 3]. Default is None.bbox_format (str, optional): The format of bounding boxes ('xywh' or 'xyxy'). Default is 'xywh'.normalized (bool, optional): Whether the bounding box coordinates are normalized. Default is True.Examples:```python# Create an Instances objectinstances = Instances(bboxes=np.array([[10, 10, 30, 30], [20, 20, 40, 40]]),segments=[np.array([[5, 5], [10, 10]]), np.array([[15, 15], [20, 20]])],keypoints=np.array([[[5, 5, 1], [10, 10, 1]], [[15, 15, 1], [20, 20, 1]]]),)```Note:The bounding box format is either 'xywh' or 'xyxy', and is determined by the `bbox_format` argument.This class does not perform input validation, and it assumes the inputs are well-formed."""# 初始化方法 __init__ 。# 1.self :类的实例本身。# 2.bboxes :一个列表或数组,包含边界框(bounding boxes)的坐标。# 3.segments :可选参数,用于表示实例的分割信息,默认为 None 。# 4.keypoints :可选参数,用于表示实例的关键点信息,默认为 None 。# 5.bbox_format :边界框的格式,默认为 "xywh" ,表示边界框由 x (左上角横坐标)、 y (左上角纵坐标)、 w (宽度)、 h (高度)组成。# 6.normalized :布尔值,表示边界框坐标是否被归一化,默认为 True 。def __init__(self, bboxes, segments=None, keypoints=None, bbox_format="xywh", normalized=True) -> None:# 使用边界框、段和关键点初始化对象。"""Initialize the object with bounding boxes, segments, and keypoints.Args:bboxes (np.ndarray): Bounding boxes, shape [N, 4].segments (list | np.ndarray, optional): Segmentation masks. Defaults to None.keypoints (np.ndarray, optional): Keypoints, shape [N, 17, 3] and format (x, y, visible). Defaults to None.bbox_format (str, optional): Format of bboxes. Defaults to "xywh".normalized (bool, optional): Whether the coordinates are normalized. Defaults to True."""# 属性初始化。# 创建一个 Bboxes 类的实例,用于存储和处理边界框信息。self._bboxes = Bboxes(bboxes=bboxes, format=bbox_format)# 存储关键点信息。self.keypoints = keypoints# 存储边界框坐标是否归一化的信息。self.normalized = normalized# 存储分割信息。self.segments = segments# 这个类的设计意图是将图像中的实例信息(边界框、关键点、分割等)封装在一起,方便后续的处理和操作。 Bboxes 是一个用于处理边界框数据的辅助类。这个设计使得 Instances 类更加模块化,易于扩展和维护。# 这段代码定义了一个名为 convert_bbox 的方法,它是 Instances 类的一个成员函数。这个方法的目的是将实例中的边界框(bounding boxes)格式转换成指定的格式。# 方法定义。# 1.self :类的实例本身。# 2.format :一个参数,表示要转换成的目标边界框格式。def convert_bbox(self, format):# 方法文档字符串。这是一个文档字符串,用于说明方法的功能。转换边界框格式。"""Convert bounding box format."""# 方法实现。这行代码调用了 self._bboxes 实例的 convert 方法,并将 format 参数传递给它。 self._bboxes 是在 Instances 类的 __init__ 方法中创建的 Bboxes 类的实例。self._bboxes.convert(format=format)# convert 的方法,该方法接受一个 format 参数,并根据这个参数将边界框的格式进行转换。例如,如果当前边界框格式是 "xywh" (表示左上角坐标和宽高),而我们想要转换成 "xyxy" (表示左上角和右下角坐标),可以调用 convert_bbox 方法并传入 "xyxy" 作为参数。# 这段代码为 Instances 类添加了一个名为 bbox_areas 的属性装饰器( @property ),它允许我们像访问属性一样访问一个方法,而不是直接调用它。这个属性装饰器定义了一个方法,用于计算实例中所有边界框的面积。# 属性装饰器。@property 是一个装饰器,它将一个方法转换为属性。这意味着你可以通过 instance.bbox_areas 来访问这个方法的返回值,而不是通过 instance.bbox_areas() 来调用它。@propertydef bbox_areas(self):# 计算边界框的面积。"""Calculate the area of bounding boxes."""# 这行代码调用了 self._bboxes 实例的 areas 方法,并返回其结果。 self._bboxes 是 Bboxes 类的一个实例,它在 Instances 类的 __init__ 方法中被创建。return self._bboxes.areas()# areas 方法不接受任何参数,并返回一个包含所有边界框面积的列表。每个面积的计算通常是通过公式 width * height 来完成的,其中 width 和 height 是边界框的宽度和高度。# 使用这个属性装饰器的好处是,你可以在代码中直接访问 bbox_areas 属性,而不需要显式调用一个方法,使得代码更加简洁和易读。# 这段代码定义了一个名为 scale 的方法,它是 Instances 类的一个成员函数。这个方法用于对实例中的边界框、分割和关键点进行缩放操作。# 方法定义。# self :类的实例本身。# scale_w :水平方向的缩放因子。# scale_h :垂直方向的缩放因子。# bbox_only :一个布尔值参数,表示是否只对边界框进行缩放,默认为 False 。def scale(self, scale_w, scale_h, bbox_only=False):# 类似于 denormalize func 但没有规范化的符号。"""Similar to denormalize func but without normalized sign."""# 边界框缩放。这行代码调用了 self._bboxes 实例的 mul 方法,并将边界框的宽度和高度分别乘以 scale_w 和 scale_h 。self._bboxes.mul(scale=(scale_w, scale_h, scale_w, scale_h))# 分割和关键点缩放。# 如果 bbox_only 参数为 True ,则方法在缩放边界框后立即返回,不进行后续的分割和关键点缩放。if bbox_only:return# 如果 segments 属性存在,这段代码将分割的 x 坐标乘以 scale_w ,y 坐标乘以 scale_h 。self.segments[..., 0] *= scale_wself.segments[..., 1] *= scale_h# 如果 keypoints 属性存在,这段代码将关键点的 x 坐标乘以 scale_w ,y 坐标乘以 scale_h 。if self.keypoints is not None:self.keypoints[..., 0] *= scale_wself.keypoints[..., 1] *= scale_h# 这个方法允许你根据给定的缩放因子调整边界框、分割和关键点的大小,这在图像处理和目标检测中是很常见的需要。例如,当你需要将检测到的目标应用到不同分辨率的图像上时,就需要对这些坐标进行缩放。通过设置 bbox_only=True ,你可以只对边界框进行缩放,而不改变分割和关键点。# 这段代码定义了一个名为 denormalize 的方法,它是 Instances 类的一个成员函数。这个方法用于将归一化的边界框、分割和关键点坐标转换为实际的像素坐标。# 方法定义。# 1.self :类的实例本身。# 2.w :图像的宽度。# 3.h :图像的高度。def denormalize(self, w, h):# 从标准化坐标中对框、段和关键点进行非标准化处理。"""Denormalizes boxes, segments, and keypoints from normalized coordinates."""# 检查归一化状态。# 如果实例的 normalized 属性为 False ,表示坐标已经是未归一化的,不需要进行任何操作,直接返回。if not self.normalized:return# 边界框去归一化# 这行代码调用了 self._bboxes 实例的 mul 方法,并将边界框的 x、y 坐标以及宽度和高度分别乘以图像的宽度和高度,从而将 归一化的边界框坐标 转换为 实际像素坐标 。self._bboxes.mul(scale=(w, h, w, h))# 分割去归一化。如果 segments 属性存在,这段代码将分割的 x 坐标乘以图像宽度 w ,y 坐标乘以图像高度 h 。self.segments[..., 0] *= wself.segments[..., 1] *= h# 关键点去归一化。如果 keypoints 属性存在,这段代码将关键点的 x 坐标乘以图像宽度 w ,y 坐标乘以图像高度 h 。if self.keypoints is not None:self.keypoints[..., 0] *= wself.keypoints[..., 1] *= h# 更新归一化状态。最后,将 normalized 属性设置为 False ,表示坐标已经从归一化状态转换为未归一化状态。self.normalized = False# 这个方法允许你将归一化的坐标转换为实际的像素坐标,这在图像处理和目标检测中是很常见的需要。归一化坐标通常用于简化计算和比较,但在实际应用中,如绘制边界框或处理图像时,需要将这些坐标转换回实际的像素坐标。# 这段代码定义了一个名为 normalize 的方法,它是 Instances 类的一个成员函数。这个方法用于将边界框、分割和关键点坐标归一化到图像的尺寸。# 方法定义。# 1.self :类的实例本身。# 2.w :图像的宽度。# 3.h :图像的高度。def normalize(self, w, h):# 将边界框、线段和关键点标准化为图像尺寸。"""Normalize bounding boxes, segments, and keypoints to image dimensions."""# 检查归一化状态。 如果实例的 normalized 属性已经是 True ,表示坐标已经是归一化的,不需要进行任何操作,直接返回。if self.normalized:return# 边界框归一化。这行代码调用了 self._bboxes 实例的 mul 方法,并将边界框的 x、y 坐标以及宽度和高度分别除以图像的宽度和高度,从而将边界框坐标归一化到 [0, 1] 的范围内。self._bboxes.mul(scale=(1 / w, 1 / h, 1 / w, 1 / h))# 分割归一化。如果 segments 属性存在,这段代码将分割的 x 坐标除以图像宽度 w ,y 坐标除以图像高度 h 。self.segments[..., 0] /= wself.segments[..., 1] /= h# 关键点归一化。如果 keypoints 属性存在,这段代码将关键点的 x 坐标除以图像宽度 w ,y 坐标除以图像高度 h 。if self.keypoints is not None:self.keypoints[..., 0] /= wself.keypoints[..., 1] /= h# 更新归一化状态。最后,将 normalized 属性设置为 True ,表示坐标已经从实际像素坐标转换为归一化状态。self.normalized = True# 这个方法允许你将实际像素坐标转换为归一化坐标,这在图像处理和目标检测中是很常见的需要。归一化坐标通常用于简化计算和比较,因为它们不依赖于图像的具体尺寸。通过归一化,可以在不同尺寸的图像之间一致地处理坐标。# 这段代码定义了一个名为 add_padding 的方法,它是 Instances 类的一个成员函数。这个方法用于处理图像的边缘填充(padding)情况,特别是在处理图像拼接(如在数据增强中的mosaic技术)时。# 方法定义。# 1.self :类的实例本身。# 2.padw :水平方向的填充宽度。# 3.padh :垂直方向的填充高度。def add_padding(self, padw, padh):# 处理矩形和马赛克情况。"""Handle rect and mosaic situation."""# 断言检查。这行代码使用 assert 语句确保 self.normalized 为 False ,即坐标是未归一化的绝对坐标。如果 self.normalized 为 True ,则会抛出异常,提示应该使用绝对坐标添加填充。assert not self.normalized, "you should add padding with absolute coordinates." # 您应该添加具有绝对坐标的填充。# 边界框添加填充。这行代码调用了 self._bboxes 实例的 add 方法,并将边界框的左上角坐标以及宽度和高度分别加上填充值,从而处理图像的边缘填充。self._bboxes.add(offset=(padw, padh, padw, padh))# 分割添加填充。如果 segments 属性存在,这段代码将分割的 x 坐标加上 padw ,y 坐标加上 padh 。self.segments[..., 0] += padwself.segments[..., 1] += padh# 关键点添加填充。如果 keypoints 属性存在,这段代码将关键点的 x 坐标加上 padw ,y 坐标加上 padh 。if self.keypoints is not None:self.keypoints[..., 0] += padwself.keypoints[..., 1] += padh# 这个方法允许你在图像处理过程中,特别是在进行数据增强或者处理拼接图像时,对边界框、分割和关键点坐标进行调整,以适应图像的边缘填充。通过添加填充,可以确保在图像拼接后,所有的坐标仍然指向正确的位置。# 这段代码定义了一个名为 __getitem__ 的特殊方法,它是 Instances 类的一个成员函数。这个方法使得 Instances 类的实例可以通过索引来访问,类似于列表或数组。这通常用于获取实例中的一个子集或特定项。# 方法定义。# 1.self :类的实例本身。# 2.index :用于访问实例中特定项的索引。# -> "Instances" :方法的返回类型注解,表示这个方法返回一个 Instances 类的实例。def __getitem__(self, index) -> "Instances":# 使用索引检索特定实例或一组实例。# 注意:# 使用布尔索引时,请确保提供长度与实例数相同的布尔数组。"""Retrieve a specific instance or a set of instances using indexing.Args:index (int, slice, or np.ndarray): The index, slice, or boolean array to selectthe desired instances.Returns:Instances: A new Instances object containing the selected bounding boxes,segments, and keypoints if present.Note:When using boolean indexing, make sure to provide a boolean array with the samelength as the number of instances."""# 获取分割信息。如果 self.segments 不为空,使用 index 来获取特定索引的分割信息;如果为空,则返回整个 self.segments 。segments = self.segments[index] if len(self.segments) else self.segments# 获取关键点信息。如果 self.keypoints 不为 None ,使用 index 来获取特定索引的关键点信息;如果为 None ,则返回 None 。keypoints = self.keypoints[index] if self.keypoints is not None else None# 获取边界框信息。使用 index 来获取特定索引的边界框信息。这里假设 self.bboxes 是一个可以通过索引访问的对象,如列表或数组。bboxes = self.bboxes[index]# 获取边界框格式。获取边界框的格式,这信息将用于创建新的 Instances 实例。bbox_format = self._bboxes.format# 返回新的 Instances 实例。使用获取的信息创建并返回一个新的 Instances 实例。return Instances(bboxes=bboxes,segments=segments,keypoints=keypoints,bbox_format=bbox_format,normalized=self.normalized,)# 这个方法允许 Instances 类的实例表现得像一个序列,可以通过索引来访问其中的元素。这在处理图像中的多个目标时非常有用,因为你可以轻松地访问每个目标的信息。例如,如果你有一个包含多个边界框的 Instances 实例,你可以通过 instances[0] 来访问第一个边界框及其相关信息。# 这段代码定义了一个名为 flipud 的方法,它是 Instances 类的一个成员函数。这个方法用于垂直翻转(上下翻转)实例中的边界框、分割和关键点坐标。# 方法定义。# 1.self :类的实例本身。# 2.h :图像的高度。def flipud(self, h):# 垂直翻转边界框、线段和关键点的坐标。"""Flips the coordinates of bounding boxes, segments, and keypoints vertically."""# 边界框垂直翻转。# 如果边界框的格式是 "xyxy" (表示左上角和右下角坐标)。if self._bboxes.format == "xyxy":# 首先,复制边界框的 y 坐标值。y1 = self.bboxes[:, 1].copy()y2 = self.bboxes[:, 3].copy()# 然后,将边界框的上边缘 y 坐标设置为 h - y2 (即下边缘的翻转位置),下边缘 y 坐标设置为 h - y1 (即上边缘的翻转位置)。self.bboxes[:, 1] = h - y2self.bboxes[:, 3] = h - y1# 如果边界框的格式不是 "xyxy" (例如 "xywh" 表示左上角坐标和宽高)。else:# 直接将边界框的 y 坐标设置为 h - self.bboxes[:, 1] ,实现垂直翻转。self.bboxes[:, 1] = h - self.bboxes[:, 1]# 分割垂直翻转。如果 segments 属性存在,这段代码将分割的 y 坐标设置为 h - self.segments[..., 1] ,实现垂直翻转。self.segments[..., 1] = h - self.segments[..., 1]# 关键点垂直翻转。如果 keypoints 属性存在,这段代码将关键点的 y 坐标设置为 h - self.keypoints[..., 1] ,实现垂直翻转。if self.keypoints is not None:self.keypoints[..., 1] = h - self.keypoints[..., 1]# 这个方法允许你将实例中的坐标垂直翻转,这在图像处理和目标检测中是很常见的需要,特别是在数据增强或者处理图像翻转时。通过这种方法,可以确保在图像垂直翻转后,所有的坐标仍然指向正确的位置。# 这段代码定义了一个名为 fliplr 的方法,它是 Instances 类的一个成员函数。这个方法用于水平翻转(左右翻转)实例中的边界框、分割和关键点坐标。# 方法定义。# 1.self :类的实例本身。# 2.w :图像的宽度。def fliplr(self, w):# 水平反转边界框和线段的顺序。"""Reverses the order of the bounding boxes and segments horizontally."""# 边界框水平翻转。# 如果边界框的格式是 "xyxy" (表示左上角和右下角坐标)。if self._bboxes.format == "xyxy":# 首先,复制边界框的 x 坐标值。x1 = self.bboxes[:, 0].copy()x2 = self.bboxes[:, 2].copy()# 然后,将边界框的左边缘 x 坐标设置为 w - x2 (即右边缘的翻转位置),右边缘 x 坐标设置为 w - x1 (即左边缘的翻转位置)。self.bboxes[:, 0] = w - x2self.bboxes[:, 2] = w - x1# 如果边界框的格式不是 "xyxy" (例如 "xywh" 表示左上角坐标和宽高)。else:# 直接将边界框的 x 坐标设置为 w - self.bboxes[:, 0] ,实现水平翻转。self.bboxes[:, 0] = w - self.bboxes[:, 0]# 分割水平翻转。如果 segments 属性存在,这段代码将分割的 x 坐标设置为 w - self.segments[..., 0] ,实现水平翻转。self.segments[..., 0] = w - self.segments[..., 0]# 关键点水平翻转。如果 keypoints 属性存在,这段代码将关键点的 x 坐标设置为 w - self.keypoints[..., 0] ,实现水平翻转。if self.keypoints is not None:self.keypoints[..., 0] = w - self.keypoints[..., 0]# 这个方法允许你将实例中的坐标水平翻转,这在图像处理和目标检测中是很常见的需要,特别是在数据增强或者处理图像翻转时。通过这种方法,可以确保在图像水平翻转后,所有的坐标仍然指向正确的位置。# 这段代码定义了一个名为 clip 的方法,它是 Instances 类的一个成员函数。这个方法用于将边界框、分割和关键点坐标裁剪到图像边界内,确保它们不会超出图像的尺寸。# 方法定义。# 1.self :类的实例本身。# 2.w :图像的宽度。# 3.h :图像的高度。def clip(self, w, h):# 剪辑边界框、线段和关键点值以保持在图像边界内。"""Clips bounding boxes, segments, and keypoints values to stay within image boundaries."""# 保存原始边界框格式。保存边界框的原始格式,以便在裁剪后可以转换回原始格式。ori_format = self._bboxes.format# 转换边界框格式为 xyxy。 将边界框格式转换为 "xyxy" ,这样可以统一处理边界框的左上角和右下角坐标。self.convert_bbox(format="xyxy")# 裁剪边界框坐标。使用 clip 方法将边界框的 x 坐标裁剪到 [0, w] 范围内,y 坐标裁剪到 [0, h] 范围内。self.bboxes[:, [0, 2]] = self.bboxes[:, [0, 2]].clip(0, w)self.bboxes[:, [1, 3]] = self.bboxes[:, [1, 3]].clip(0, h)# 恢复原始边界框格式。如果原始边界框格式不是 "xyxy" ,则将边界框格式转换回原始格式。if ori_format != "xyxy":self.convert_bbox(format=ori_format)# 裁剪分割坐标。使用 clip 方法将分割的 x 坐标裁剪到 [0, w] 范围内,y 坐标裁剪到 [0, h] 范围内。self.segments[..., 0] = self.segments[..., 0].clip(0, w)self.segments[..., 1] = self.segments[..., 1].clip(0, h)# 裁剪关键点坐标。如果 keypoints 属性存在,使用 clip 方法将关键点的 x 坐标裁剪到 [0, w] 范围内,y 坐标裁剪到 [0, h] 范围内。if self.keypoints is not None:self.keypoints[..., 0] = self.keypoints[..., 0].clip(0, w)self.keypoints[..., 1] = self.keypoints[..., 1].clip(0, h)# 这个方法确保了在图像处理过程中,所有的坐标都不会超出图像的边界,这在图像裁剪、缩放或数据增强时非常有用。通过裁剪操作,可以避免坐标超出图像尺寸导致的计算错误或异常。# 这段代码定义了一个名为 remove_zero_area_boxes 的方法,它是 Instances 类的一个成员函数。这个方法用于移除那些面积为零的边界框,这通常发生在边界框被裁剪到图像边界后,某些边界框的宽度或高度变为零。# 方法定义。# 1.self :类的实例本身。def remove_zero_area_boxes(self):# 删除零面积框,即剪辑后某些框可能具有零宽度或高度。"""Remove zero-area boxes, i.e. after clipping some boxes may have zero width or height."""# 计算边界框面积并检查有效性。使用 self.bbox_areas 属性(之前定义的 @property )来获取所有边界框的面积,并创建一个布尔数组 good ,其中面积大于零的边界框被认为是有效的。good = self.bbox_areas > 0# 检查是否有无效的边界框。使用 all 函数检查是否所有的边界框都是有效的。如果没有(即存在无效的边界框),则执行以下操作。if not all(good):# 移除无效的边界框。使用布尔索引 good 来更新 self._bboxes ,只保留有效的边界框。self._bboxes = self._bboxes[good]# 更新分割信息。如果 self.segments 不为空,使用布尔索引 good 来更新分割信息,只保留与有效边界框对应的分割。if len(self.segments):self.segments = self.segments[good]# 更新关键点信息。 如果 self.keypoints 不为 None ,使用布尔索引 good 来更新关键点信息,只保留与有效边界框对应的关键点。if self.keypoints is not None:self.keypoints = self.keypoints[good]# 返回有效性数组。 方法返回布尔数组 good ,表示每个边界框是否有效。return good# 这个方法确保了在图像处理过程中,所有的边界框都是有效的,没有宽度或高度为零的边界框。这在目标检测和图像标注任务中非常重要,因为无效的边界框可能会导致错误或异常。通过移除这些无效的边界框,可以保证后续处理的准确性和稳定性。# 这段代码定义了一个名为 update 的方法,它是 Instances 类的一个成员函数。这个方法用于更新实例中的边界框、分割和关键点信息。# 方法定义。# 1.self :类的实例本身。# 2.bboxes :新的边界框数据。# 3.segments :可选参数,新的分割数据,默认为 None 。# 4.keypoints :可选参数,新的关键点数据,默认为 None 。def update(self, bboxes, segments=None, keypoints=None):# 更新实例变量。"""Updates instance variables."""# 更新边界框信息。创建一个新的 Bboxes 类实例,使用新的边界框数据 bboxes 和当前实例的边界框格式。这个新的 Bboxes 实例替换掉原来的 self._bboxes 。self._bboxes = Bboxes(bboxes, format=self._bboxes.format)# 更新分割信息。如果提供了新的分割数据 segments ,则更新 self.segments 属性。if segments is not None:self.segments = segments# 更新关键点信息。如果提供了新的关键点数据 keypoints ,则更新 self.keypoints 属性。if keypoints is not None:self.keypoints = keypoints# 这个方法允许你更新 Instances 实例中的边界框、分割和关键点信息。这在处理动态变化的数据或者在数据增强、跟踪等场景中非常有用,因为你可能需要根据新的数据更新实例的状态。通过这个方法,可以确保实例中的信息是最新的,从而进行后续的处理和分析。# 这段代码定义了一个名为 __len__ 的特殊方法,它是 Instances 类的一个成员函数。这个方法使得 Instances 类的实例可以像列表或元组那样使用内置的 len() 函数来获取长度。# 方法定义。# 1.self :类的实例本身。def __len__(self):# 返回实例列表的长度。"""Return the length of the instance list."""# 返回边界框列表的长度。这行代码返回 self.bboxes 的长度,即边界框的数量。这里假设 self.bboxes 是一个可以通过 len() 函数获取长度的对象,如列表或数组。return len(self.bboxes)# 通过实现 __len__ 方法, Instances 类的实例现在可以响应 len() 函数的调用。例如,如果你有一个 Instances 实例 instances ,你可以通过 len(instances) 来获取其中包含的边界框数量。这使得 Instances 类更易于与期望长度信息的Python代码集成,例如在循环或条件语句中。# 这段代码定义了一个名为 concatenate 的类方法,它是 Instances 类的一个成员函数。这个方法用于将多个 Instances 实例的边界框、分割和关键点数据沿着指定轴进行拼接。# 这个装饰器表示 concatenate 是一个类方法,它的第一个参数是 cls ,代表类本身,而不是类的实例。@classmethod# 1.cls :类方法的装饰器,表示这个方法是类级别的方法,可以通过类名直接调用,也可以通过类的实例调用。# 2.instances_list :一个包含多个 Instances 实例的列表或元组。# 3.axis :指定拼接的轴,默认为0。# -> "Instances" :方法的返回类型注解,表示这个方法返回一个 Instances 类的实例。def concatenate(cls, instances_list: List["Instances"], axis=0) -> "Instances":# 将实例对象列表连接成单个实例对象。# 注意:# 列表中的 `Instances` 对象应具有相同的属性,例如边界框的格式、关键点是否存在以及坐标是否标准化。"""Concatenates a list of Instances objects into a single Instances object.Args:instances_list (List[Instances]): A list of Instances objects to concatenate.axis (int, optional): The axis along which the arrays will be concatenated. Defaults to 0.Returns:Instances: A new Instances object containing the concatenated bounding boxes,segments, and keypoints if present.Note:The `Instances` objects in the list should have the same properties, such asthe format of the bounding boxes, whether keypoints are present, and if thecoordinates are normalized."""# 断言检查。# 确保 instances_list 是列表或元组,并且不为空。assert isinstance(instances_list, (list, tuple))if not instances_list:return cls(np.empty(0))# 确保 instances_list 中的所有元素都是 Instances 实例。assert all(isinstance(instance, Instances) for instance in instances_list)# 处理单个实例的情况。如果列表中只有一个实例,直接返回这个实例。if len(instances_list) == 1:return instances_list[0]# 确定拼接参数。# 确定是否需要拼接关键点数据。use_keypoint = instances_list[0].keypoints is not None# 获取 边界框的格式 和 归一化状态 ,这些将用于创建新的 Instances 实例。bbox_format = instances_list[0]._bboxes.formatnormalized = instances_list[0].normalized# 拼接数据。# 使用 np.concatenate 函数沿着指定轴拼接所有实例的边界框、分割和关键点数据。cat_boxes = np.concatenate([ins.bboxes for ins in instances_list], axis=axis)cat_segments = np.concatenate([b.segments for b in instances_list], axis=axis)cat_keypoints = np.concatenate([b.keypoints for b in instances_list], axis=axis) if use_keypoint else None# 创建并返回新的 Instances 实例。使用拼接后的数据和原始参数创建一个新的 Instances 实例,并返回这个实例。return cls(cat_boxes, cat_segments, cat_keypoints, bbox_format, normalized)# 这个方法允许你将多个 Instances 实例的数据合并为一个实例,这在处理多个图像或数据批次时非常有用,特别是当你需要将这些数据作为一个整体进行进一步处理时。通过这个方法,可以轻松地将多个实例的数据拼接在一起,而不需要手动处理每个属性。# 这段代码定义了一个名为 bboxes 的属性装饰器( @property ),它是 Instances 类的一个成员函数。这个装饰器使得我们可以像访问属性一样访问 self._bboxes.bboxes 方法,而不是直接调用它。# 属性装饰器。@property 是一个装饰器,它将一个方法转换为属性。这意味着你可以通过 instance.bboxes 来访问这个方法的返回值,而不是通过 instance.bboxes() 来调用它。@propertydef bboxes(self):# 返回边界框。"""Return bounding boxes."""# 返回边界框数据。这行代码返回 self._bboxes 实例的 bboxes 属性,它包含了边界框的数据。return self._bboxes.bboxes# 通过使用 @property 装饰器, Instances 类的实例现在可以通过 bboxes 属性来访问边界框数据,使得代码更加简洁和易读