instance.py
ultralytics\utils\instance.py
目录
[2.def _ntuple(n):](#2.def _ntuple(n):)
[3.class Bboxes:](#3.class Bboxes:)
[4.class Instances:](#4.class Instances:)
1.所需的库和模块
python
# Ultralytics YOLO 🚀, AGPL-3.0 license
from collections import abc
from itertools import repeat
from numbers import Number
from typing import List
import numpy as np
from .ops import ltwh2xywh, ltwh2xyxy, xywh2ltwh, xywh2xyxy, xyxy2ltwh, xyxy2xywh
2.def _ntuple(n):
python
# 这段代码定义了一个名为 _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:
python
# 这段代码定义了一个名为 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 == 2
assert bboxes.shape[1] == 4
# 这两行代码将传入的 bboxes 和 format 保存为实例变量,以便在类的其他方法中使用。
self.bboxes = bboxes
self.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 xyxy
if 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 select
the 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 same
length 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:
python
# 这段代码定义了一个名为 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 object
instances = 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() 来调用它。
@property
def 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_w
self.segments[..., 1] *= scale_h
# 如果 keypoints 属性存在,这段代码将关键点的 x 坐标乘以 scale_w ,y 坐标乘以 scale_h 。
if self.keypoints is not None:
self.keypoints[..., 0] *= scale_w
self.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] *= w
self.segments[..., 1] *= h
# 关键点去归一化。如果 keypoints 属性存在,这段代码将关键点的 x 坐标乘以图像宽度 w ,y 坐标乘以图像高度 h 。
if self.keypoints is not None:
self.keypoints[..., 0] *= w
self.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] /= w
self.segments[..., 1] /= h
# 关键点归一化。如果 keypoints 属性存在,这段代码将关键点的 x 坐标除以图像宽度 w ,y 坐标除以图像高度 h 。
if self.keypoints is not None:
self.keypoints[..., 0] /= w
self.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] += padw
self.segments[..., 1] += padh
# 关键点添加填充。如果 keypoints 属性存在,这段代码将关键点的 x 坐标加上 padw ,y 坐标加上 padh 。
if self.keypoints is not None:
self.keypoints[..., 0] += padw
self.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 select
the 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 same
length 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 - y2
self.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 - x2
self.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 as
the format of the bounding boxes, whether keypoints are present, and if the
coordinates 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.format
normalized = 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() 来调用它。
@property
def bboxes(self):
# 返回边界框。
"""Return bounding boxes."""
# 返回边界框数据。这行代码返回 self._bboxes 实例的 bboxes 属性,它包含了边界框的数据。
return self._bboxes.bboxes
# 通过使用 @property 装饰器, Instances 类的实例现在可以通过 bboxes 属性来访问边界框数据,使得代码更加简洁和易读