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 ,该函数可以将输入转换为一个长度为 n 的元组。
# 定义一个函数 _ntuple ,接受一个参数。
# 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."""
# abc.Iterable
# abc.Iterable 是 Python 中 collections.abc 模块定义的一个抽象基类(Abstract Base Class, ABC),用于检查一个对象是否是可迭代的。可迭代对象是指可以被迭代器遍历的对象,例如列表、元组、字典、集合等。
# 定义 :
# abc.Iterable 是一个抽象基类,它定义了可迭代对象的接口。一个对象如果实现了 __iter__() 方法或者 __getitem__() 方法(并且可以接受从零开始的整数索引),则被认为是可迭代的。
# 用途 :
# 通过继承 abc.Iterable ,可以确保一个类的对象是可迭代的。此外,可以使用 isinstance() 函数来检查一个对象是否是可迭代的,例如 isinstance(obj, abc.Iterable) 。
# abc.Iterable 是一个非常有用的抽象基类,它提供了一种标准的方式来定义和检查可迭代对象。通过继承 abc.Iterable 或使用 isinstance() 函数,可以确保对象具有可迭代的特性,从而在需要迭代的场景中使用这些对象。
# itertools.repeat(object[, times])
# repeat() 函数是 Python 标准库 itertools 模块中的一个函数,它用于创建一个迭代器,该迭代器会无限次重复给定的值。
# 参数 :
# object :要重复的值。
# times :(可选)重复的次数。如果不提供或为 None ,则迭代器将无限重复给定的值。
# 返回值 :
# 返回一个迭代器,该迭代器重复给定的值。
# repeat() 函数常用于需要固定值的场景,例如在 zip() 函数中为每个元素对提供相同的参数,或者在其他需要重复值的迭代处理中。
# isinstance(x, abc.Iterable) :检查 x 是否是一个可迭代对象。 abc.Iterable 是 collections.abc 模块中的一个抽象基类,用于检查对象是否可迭代。
# 如果 x 是可迭代对象,则直接返回 x ,因为 x 已经是可迭代的,不需要进一步处理。
# 如果 x 不是可迭代对象,则使用 tuple(repeat(x, n)) 将 x 转换为一个长度为 n 的元组。 repeat(x, n) :使用 itertools.repeat 函数生成一个可迭代对象,该对象重复 x 共 n 次。 tuple(...) :将生成的可迭代对象转换为元组。
return x if isinstance(x, abc.Iterable) else tuple(repeat(x, n))
# 返回嵌套函数 parse ,这样 _ntuple 函数实际上返回的是一个函数,而不是直接返回一个值。
return parse
# 这个函数 _ntuple 的主要作用是创建一个转换函数 parse ,该函数可以将输入转换为一个长度为 n 的元组。如果输入本身是可迭代的,则直接返回;否则,将输入重复 n 次并转换为元组。这种设计通常用于确保输入参数具有统一的格式,例如在处理多维数据或配置参数时,确保参数以元组的形式存在。
# to_2tuple 和 to_4tuple 是通过调用 _ntuple 函数创建的两个函数对象,分别用于将输入转换为长度为 2 和 4 的元组。
to_2tuple = _ntuple(2)
to_4tuple = _ntuple(4)
# 这种设计模式在深度学习和计算机视觉库中很常见,用于确保输入参数具有统一的格式。例如,在处理图像的尺寸参数时,确保尺寸参数始终是一个元组,可以简化后续的处理逻辑。
# `xyxy` means left top and right bottom `xyxy` 表示左上角和右下角。
# `xywh` means center x, center y and width, height(YOLO format) `xywh` 表示中心 x、中心 y 和宽度、高度(YOLO 格式)。
# `ltwh` means left top and width, height(COCO format) `ltwh` 表示左上角和宽度、高度(COCO 格式)。
# _formats 是一个列表,包含了一些字符串,表示不同的边界框(bounding box)格式。这在处理图像标注和目标检测任务时非常有用,因为不同的任务或库可能使用不同的边界框表示方式。
_formats = ["xyxy", "xywh", "ltwh"]
# __all__ 是一个特殊的变量,用于指定模块的公共接口,即哪些名称应该在从该模块导入时被导出。帮助用户在导入模块时了解哪些名称是可用的,同时也防止模块内部的私有名称被意外导出。
# 在这里, __all__ 是一个元组,包含一个字符串 "Bboxes" ,表示 Bboxes 是该模块的公共接口之一。
# 使用元组而不是列表来定义 __all__ 是一种常见的做法,因为元组是不可变的,可以防止意外修改。
__all__ = ("Bboxes",) # tuple or list
3.class Bboxes:
python
# 这段代码定义了一个名为 Bboxes 的类,用于处理和操作边界框(bounding boxes),它支持多种边界框格式,并提供了一些常用的操作方法。
# 定义一个名为 Bboxes 的类。
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 类的构造函数 __init__ 的定义,用于初始化一个 Bboxes 实例。
# 定义构造函数 __init__ ,接受两个参数。返回类型为 None ,表示该方法不返回任何值。
# 1.bboxes :边界框的数组或列表,表示要处理的边界框数据。
# 2.format :边界框的格式,默认为 "xyxy" 。
def __init__(self, bboxes, format="xyxy") -> None:
# 使用指定格式的边界框数据初始化 Bboxes 类。
"""Initializes the Bboxes class with bounding box data in a specified format."""
# 使用 assert 语句检查 format 是否在预定义的格式列表 _formats 中。如果 format 不在列表中,则抛出异常,提示无效的边界框格式,并列出允许的格式。
assert format in _formats, f"Invalid bounding box format: {format}, format must be one of {_formats}" # 边界框格式无效:{format},格式必须是 {_formats} 之一。
# 如果 bboxes 是一维数组(即只有一个边界框),则使用 bboxes[None, :] 将其扩展为二维数组。这一步是为了确保 bboxes 始终是二维的,以便后续操作的一致性。
# bboxes.ndim == 1 :检查 bboxes 是否为一维数组。
# bboxes[None, :] :在 bboxes 的前面添加一个新维度,使其变为二维数组。
bboxes = bboxes[None, :] if bboxes.ndim == 1 else bboxes
# 断言检查 bboxes 是否为二维数组。如果不是,则抛出异常,提示边界框数据必须是二维的。
assert bboxes.ndim == 2
# 断言检查 bboxes 的第二维(即每行的元素数量)是否为 4。因为每个边界框由 4 个坐标组成,所以每行必须有 4 个元素。如果不是,则抛出异常,提示边界框数据的形状不正确。
assert bboxes.shape[1] == 4
# 将处理后的 bboxes 数组存储在实例变量 self.bboxes 中,以便后续使用。
self.bboxes = bboxes
# 将边界框的格式 format 存储在实例变量 self.format 中,以便后续操作时知道当前边界框的格式。
self.format = format
# 这行代码被注释掉了,但可以推测它原本用于存储边界框是否被归一化的状态。如果需要,可以取消注释并添加相应的参数和逻辑来支持归一化状态的管理。
# self.normalized = normalized
# 这个构造函数的主要作用是初始化一个 Bboxes 实例,确保输入的边界框数据是有效的,并将其存储在实例变量中。通过检查边界框的格式和形状,构造函数确保了后续操作的正确性和一致性。
# 这段代码是 Bboxes 类中的 convert 方法,用于将边界框的格式从当前格式转换为指定的目标格式。
# 定义 convert 方法,接受一个参数。
# 1.format :表示要转换到的目标格式。
def convert(self, format):
# 将边界框格式从一种类型转换为另一种类型。
"""Converts bounding box format from one type to another."""
# 使用 assert 语句检查 format 是否在预定义的格式列表 _formats 中。如果 format 不在列表中,则抛出异常,提示无效的边界框格式,并列出允许的格式。
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" 。
elif self.format == "xyxy":
# 如果目标格式是 "xywh" ,则选择转换函数 xyxy2xywh ,将 "xyxy" 格式转换为 "xywh"。 否则,选择转换函数 xyxy2ltwh ,将 "xyxy" 格式转换为 "ltwh" 。
func = xyxy2xywh if format == "xywh" else xyxy2ltwh
# 如果当前格式是 "xywh" 。
elif self.format == "xywh":
# 如果目标格式是 "xyxy" ,则选择转换函数 xywh2xyxy ,将 "xywh" 格式转换为 "xyxy" 。否则,选择转换函数 xywh2ltwh ,将 "xywh" 格式转换为 "ltwh" 。
func = xywh2xyxy if format == "xyxy" else xywh2ltwh
# 如果当前格式是 "ltwh" 。
else:
# 如果目标格式是 "xyxy" ,则选择转换函数 ltwh2xyxy ,将 "ltwh" 格式转换为 "xyxy" 。否则,选择转换函数 ltwh2xywh ,将 "ltwh" 格式转换为 "xywh" 。
func = ltwh2xyxy if format == "xyxy" else ltwh2xywh
# 使用选择的转换函数 func 对 self.bboxes 进行格式转换,并将转换后的结果重新赋值给 self.bboxes 。
self.bboxes = func(self.bboxes)
# 更新 self.format ,将其设置为目标格式 format ,以反映当前边界框的格式已更改。
self.format = format
# 这个 convert 方法的主要作用是根据当前边界框的格式和目标格式,选择合适的转换函数进行格式转换,并更新边界框数据和格式状态。通过这种方式,可以灵活地在不同的边界框格式之间进行转换,满足不同场景下的需求。
# 这段代码是 Bboxes 类中的 areas 方法,用于计算边界框的面积。
# 定义 areas 方法,该方法不接受额外的参数,仅使用实例自身的数据来计算面积。
def areas(self):
# 返回边界框面积。
"""Return box areas."""
# 调用 self.convert("xyxy") 方法,将边界框的格式转换为 "xyxy" 格式。这是因为计算面积的公式适用于 "xyxy" 格式,其中 (x1, y1) 是左上角坐标, (x2, y2) 是右下角坐标。
# 如果边界框已经是 "xyxy" 格式,则 convert 方法不会进行任何操作。
self.convert("xyxy")
# 计算每个边界框的面积 :
# self.bboxes[:, 2] - self.bboxes[:, 0] :计算每个边界框的宽度,即右下角的 x 坐标减去左上角的 x 坐标。
# self.bboxes[:, 3] - self.bboxes[:, 1] :计算每个边界框的高度,即右下角的 y 坐标减去左上角的 y 坐标。
# 将宽度和高度相乘,得到每个边界框的面积。
# 返回一个数组,包含每个边界框的面积.
return (self.bboxes[:, 2] - self.bboxes[:, 0]) * (self.bboxes[:, 3] - self.bboxes[:, 1])
# 这个 areas 方法的主要作用是计算边界框的面积。它首先确保边界框的格式是 "xyxy" ,然后使用标准的面积计算公式 (x2 - x1) * (y2 - y1) 来计算每个边界框的面积。这种方法简单高效,适用于处理多个边界框的情况。
# 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 :表示缩放比例。 scale 可以是单个数值或一个长度为 4 的元组/列表。
def mul(self, scale):
"""
Args:
scale (tuple | list | int): the scale for four coords.
"""
# 检查 scale 是否为单个数值( Number 类型)。
if isinstance(scale, Number):
# 如果是,则调用 to_4tuple(scale) 将其转换为一个长度为 4 的元组。 to_4tuple 函数会将单个数值重复四次,形成一个元组,例如 to_4tuple(2) 会返回 (2, 2, 2, 2) 。
# 这样做的目的是确保 scale 有四个元素,分别对应边界框的四个坐标(x1, y1, x2, y2)的缩放比例.
scale = to_4tuple(scale)
# 断言检查 scale 是否为元组或列表。如果不是,则抛出异常,提示 scale 的类型不正确。
assert isinstance(scale, (tuple, list))
# 断言检查 scale 的长度是否为 4。如果不是,则抛出异常,提示 scale 的长度不正确。
assert len(scale) == 4
# 对边界框的每个坐标进行缩放。
# 将所有边界框的 x1 坐标乘以 scale[0] 。
self.bboxes[:, 0] *= scale[0]
# 将所有边界框的 y1 坐标乘以 scale[1] 。
self.bboxes[:, 1] *= scale[1]
#将所有边界框的 x2 坐标乘以 scale[2] 。
self.bboxes[:, 2] *= scale[2]
# 将所有边界框的 y2 坐标乘以 scale[3] 。
self.bboxes[:, 3] *= scale[3]
# 这个 mul 方法的主要作用是根据指定的缩放比例对边界框的坐标进行缩放。它首先处理 scale 参数,确保其为长度为 4 的元组或列表,然后分别对边界框的四个坐标进行缩放。这种方法可以灵活地对边界框进行缩放,适用于图像处理和目标检测等场景中对边界框进行尺寸调整的需求。
# 这段代码是 Bboxes 类中的 add 方法,用于将边界框的坐标按指定的偏移量移动。
# 定义 add 方法,接受一个参数。
# 1.offset :表示偏移量。 offset 可以是单个数值或一个长度为 4 的元组/列表。
def add(self, offset):
"""
Args:
offset (tuple | list | int): the offset for four coords.
"""
# 检查 offset 是否为单个数值( Number 类型)。
if isinstance(offset, Number):
# 如果是,则调用 to_4tuple(offset) 将其转换为一个长度为 4 的元组。 to_4tuple 函数会将单个数值重复四次,形成一个元组,例如 to_4tuple(2) 会返回 (2, 2, 2, 2) 。
# 这样做的目的是确保 offset 有四个元素,分别对应边界框的四个坐标(x1, y1, x2, y2)的偏移量。
offset = to_4tuple(offset)
# 断言检查 offset 是否为元组或列表。如果不是,则抛出异常,提示 offset 的类型不正确。
assert isinstance(offset, (tuple, list))
# 断言检查 offset 的长度是否为 4。如果不是,则抛出异常,提示 offset 的长度不正确。
assert len(offset) == 4
# 对边界框的每个坐标进行偏移。
self.bboxes[:, 0] += offset[0]
self.bboxes[:, 1] += offset[1]
self.bboxes[:, 2] += offset[2]
self.bboxes[:, 3] += offset[3]
# 这个 add 方法的主要作用是根据指定的偏移量对边界框的坐标进行移动。它首先处理 offset 参数,确保其为长度为 4 的元组或列表,然后分别对边界框的四个坐标进行偏移。这种方法可以灵活地对边界框进行位置调整,适用于图像处理和目标检测等场景中对边界框进行位置变换的需求。
# 这段代码是 Bboxes 类中的 __len__ 方法,用于返回边界框的数量。
# 定义 __len__ 方法。这是一个特殊的方法,用于实现 Python 的内置函数 len() 。当调用 len(instance) 时,Python 会自动调用该实例的 __len__ 方法。
def __len__(self):
# 返回边界框的数量。
"""Return the number of boxes."""
# 返回 self.bboxes 的长度,即边界框的数量。 self.bboxes 是一个二维数组,其中每一行表示一个边界框的坐标。 len(self.bboxes) 返回该数组的行数,也就是边界框的数量。
return len(self.bboxes)
# 通过实现 __len__ 方法, Bboxes 类的对象可以使用内置的 len() 函数来获取边界框的数量。这使得代码更加简洁和直观,符合 Python 的习惯用法。例如,如果 bboxes 是一个 Bboxes 实例,那么 len(bboxes) 将返回边界框的数量。
# 这段代码是 Bboxes 类中的一个类方法 concatenate ,用于将多个 Bboxes 实例合并为一个新的 Bboxes 实例。
# @classmethod
# 在Python中, @classmethod 是一个装饰器,用于将一个普通的方法转换为类方法。类方法的第一个参数总是 cls ,它代表类本身,而不是类的实例。这意味着你可以在不创建类实例的情况下调用这个方法,并且可以在这个方法内部访问类的属性和方法。
# 类方法通常用于不需要类实例就可以执行的操作,例如 :
# 创建类实例时不依赖于类的状态。
# 需要访问类属性而不是实例属性。
# 实现备选构造器(alternative constructors)。
# 类方法可以被子类继承,并且可以被子类的实例和子类本身调用。
@classmethod
# 使用 @classmethod 装饰器定义一个类方法 concatenate 。
# 1.cls :是类的引用,用于在方法中创建新的类实例。
# 2.boxes_list :是一个参数,表示要合并的 Bboxes 实例列表。
# 3.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 是否为列表或元组。如果不是,则抛出异常,提示 boxes_list 的类型不正确。
assert isinstance(boxes_list, (list, tuple))
# 如果 boxes_list 为空,则返回一个新的 Bboxes 实例,其中边界框数组为空。
if not boxes_list:
# np.empty(0) 创建一个空的 NumPy 数组。 cls(np.empty(0)) 使用类的构造函数创建一个新的 Bboxes 实例,传入空的边界框数组。
return cls(np.empty(0))
# 断言检查 boxes_list 中的每个元素是否都是 Bboxes 实例。如果有任何元素不是 Bboxes 实例,则抛出异常。
assert all(isinstance(box, Bboxes) for box in boxes_list)
# 如果 boxes_list 只有一个元素,则直接返回该元素,因为不需要合并。
if len(boxes_list) == 1:
return boxes_list[0]
# 使用 np.concatenate 将 boxes_list 中所有 Bboxes 实例的边界框数组按指定轴 axis 合并。
# [b.bboxes for b in boxes_list] 是一个列表推导式,用于提取每个 Bboxes 实例的 bboxes 属性。
# np.concatenate(..., axis=axis) 将这些边界框数组合并成一个新的数组。
# 使用类的构造函数 cls(...) 创建一个新的 Bboxes 实例,传入合并后的边界框数组,并返回该实例。
return cls(np.concatenate([b.bboxes for b in boxes_list], axis=axis))
# 这个 concatenate 类方法的主要作用是将多个 Bboxes 实例合并为一个新的 Bboxes 实例。它首先进行参数验证,确保输入的列表不为空且所有元素都是 Bboxes 实例。然后根据列表的长度决定是否需要合并,最后使用 np.concatenate 进行合并并返回新的实例。这种方法方便地实现了边界框数据的批量处理和合并。
# 这段代码是 Bboxes 类中的 __getitem__ 方法,用于实现对 Bboxes 实例的索引操作。
# 定义 __getitem__ 方法,接受一个参数。
# 1.index :表示要获取的边界框的索引。
# 返回类型为 Bboxes ,表示返回一个新的 Bboxes 实例。
def __getitem__(self, index) -> "Bboxes":
# 使用索引检索特定边界框或一组边界框。
# 引发:
# AssertionError:如果索引的边界框不形成二维矩阵。
# 注意:
# 使用布尔索引时,请确保提供长度与边界框数量相同的布尔数组。
"""
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):
# 如果是整数,则 self.bboxes[index] 获取到的是一个一维数组,表示单个边界框的坐标。
# 使用 .view(1, -1) 将一维数组转换为二维数组,形状为 (1, 4) ,以便符合 Bboxes 类的输入要求。
# 创建一个新的 Bboxes 实例,传入转换后的二维数组,并返回该实例。
return Bboxes(self.bboxes[index].view(1, -1))
# 如果 index 不是整数,则使用 index 对 self.bboxes 进行索引,获取一个子集 b , b 可能是一个二维数组或一维数组,具体取决于 index 的类型和值。
b = self.bboxes[index]
# 断言检查 b 是否为二维数组。
# 如果 b 不是二维数组,则抛出异常,提示索引操作失败,因为 Bboxes 类要求边界框数据必须是二维的。这个检查确保了返回的边界框数据格式正确,符合 Bboxes 类的内部要求。
assert b.ndim == 2, f"Indexing on Bboxes with {index} failed to return a matrix!" # 使用 {index} 对 Bboxes 进行索引无法返回矩阵!
# 创建一个新的 Bboxes 实例,传入 b ,并返回该实例。
return Bboxes(b)
# 这个 __getitem__ 方法的主要作用是实现对 Bboxes 实例的索引操作,允许用户通过索引来获取部分边界框数据。它能够处理整数索引和其他类型的索引(如切片或布尔索引),并确保返回的边界框数据格式正确,符合 Bboxes 类的要求。
# 这个 Bboxes 类提供了一个灵活的方式来处理和操作边界框。它支持多种边界框格式,并提供了转换、计算面积、缩放、偏移等操作方法。此外,还实现了索引和合并操作,使得边界框的处理更加方便和高效。
4.class 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.
"""
# 这段代码是 Instances 类的构造函数 __init__ 的定义,用于初始化一个 Instances 实例。
# 定义构造函数 __init__ ,接受五个参数。返回类型为 None ,表示该方法不返回任何值。
# 1.self :指向当前实例的引用。
# 2.bboxes :边界框数据,可以是数组或列表形式。
# 3.segments :分割掩码数据,默认为 None 。
# 4.keypoints :关键点数据,默认为 None 。
# 5.bbox_format :边界框的格式,默认为 "xywh" 。
# 6.normalized :边界框是否被归一化,默认为 True 。
def __init__(self, bboxes, segments=None, keypoints=None, bbox_format="xywh", normalized=True) -> None:
"""
Args:
bboxes (ndarray): bboxes with shape [N, 4].
segments (list | ndarray): segments.
keypoints (ndarray): keypoints(x, y, visible) with shape [N, 17, 3].
"""
# 使用 Bboxes 类创建一个边界框对象 self._bboxes ,并将 bboxes 和 bbox_format 作为参数传递给 Bboxes 的构造函数。
# 这一步将边界框数据封装在一个 Bboxes 对象中,以便后续可以利用 Bboxes 类提供的方法来操作边界框。
self._bboxes = Bboxes(bboxes=bboxes, format=bbox_format)
# 将传入的关键点数据 keypoints 存储在实例变量 self.keypoints 中。关键点数据通常用于表示图像中特定位置的特征点,例如人体姿态估计中的关节位置。
self.keypoints = keypoints
# 将传入的归一化状态 normalized 存储在实例变量 self.normalized 中。归一化状态表示边界框、分割掩码和关键点的坐标是否已经被归一化到 [0, 1] 范围内,通常用于图像处理中的坐标转换。
self.normalized = normalized
# 将传入的分割掩码数据 segments 存储在实例变量 self.segments 中。分割掩码数据用于表示图像中对象的像素级掩码,通常用于图像分割任务中。
self.segments = segments
# 这个构造函数的主要作用是初始化一个 Instances 实例,将边界框、分割掩码、关键点等数据封装在实例变量中,并记录边界框的格式和归一化状态。通过这种方式, Instances 类可以方便地管理和操作图像实例的多种信息,为后续的图像处理和分析任务提供基础。
# 这段代码是 Instances 类中的 convert_bbox 方法,用于将边界框的格式转换为指定的格式。
# 定义 convert_bbox 方法,接受一个参数。
# format :表示要转换到的目标边界框格式。
def convert_bbox(self, format):
# 转换边界框格式。
"""Convert bounding box format."""
# 调用 self._bboxes 对象的 convert 方法,传入目标格式 format 。 self._bboxes 是一个 Bboxes 类的实例,其 convert 方法负责将边界框的格式从当前格式转换为指定的目标格式。
# 这一步实际上是委托 Bboxes 类来处理具体的格式转换逻辑, Instances 类通过这种方式实现了对边界框格式的转换功能。
self._bboxes.convert(format=format)
# 这个 convert_bbox 方法的主要作用是将 Instances 实例中包含的边界框的格式转换为指定的格式。它通过调用 Bboxes 类的 convert 方法来实现格式转换,使得 Instances 类可以灵活地支持多种边界框格式,并在需要时进行转换。
# 这段代码是 Instances 类中的一个属性方法 bbox_areas ,用于获取边界框的面积。
# 使用 @property 装饰器定义一个属性方法 bbox_areas 。这意味着可以像访问实例变量一样访问 bbox_areas ,而不需要调用方法。
@property
# bbox_areas 属性用于返回边界框的面积。
def bbox_areas(self):
# 计算边界框的面积。
"""Calculate the area of bounding boxes."""
# 调用 self._bboxes 对象的 areas 方法来计算边界框的面积。 self._bboxes 是一个 Bboxes 类的实例,其 areas 方法负责计算边界框的面积。 返回计算得到的面积数组,该数组包含每个边界框的面积。
return self._bboxes.areas()
# 这个 bbox_areas 属性方法的主要作用是提供一个方便的方式来获取 Instances 实例中所有边界框的面积。通过调用 Bboxes 类的 areas 方法,它能够返回一个包含所有边界框面积的数组,使得用户可以轻松地获取和使用边界框的面积信息。
# 这段代码是 Instances 类中的 scale 方法,用于对实例中的边界框、分割掩码和关键点进行缩放。
# 定义 scale 方法,接受三个参数。
# 1.scale_w :宽度的缩放比例。
# 2.scale_h :高度的缩放比例。
# 3.bbox_only :布尔值,表示是否只缩放边界框,默认为 False 。
def scale(self, scale_w, scale_h, bbox_only=False):
# 这可能与非规范化函数类似,但没有规范化符号。
"""This might be similar with denormalize func but without normalized sign."""
# 调用 self._bboxes 对象的 mul 方法来缩放边界框。
# self._bboxes 是一个 Bboxes 类的实例,其 mul 方法负责将边界框的每个坐标按指定的比例缩放。传入的 scale 参数是一个元组 (scale_w, scale_h, scale_w, scale_h) ,分别对应边界框的四个坐标(x1, y1, x2, y2)的缩放比例。
self._bboxes.mul(scale=(scale_w, scale_h, scale_w, scale_h))
# 如果 bbox_only 为 True ,则直接返回,不进行后续的分割掩码和关键点的缩放操作。这允许用户仅缩放边界框,而不影响其他数据。
if bbox_only:
return
# 对分割掩码的 x 坐标和 y 坐标分别进行缩放。这里使用了 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 0 和 1 分别表示 x 和 y 坐标。
# 将分割掩码的所有 x 坐标乘以 scale_w 。
self.segments[..., 0] *= scale_w
# 将分割掩码的所有 y 坐标乘以 scale_h 。
self.segments[..., 1] *= scale_h
# 如果关键点数据存在,则对关键点的 x 坐标和 y 坐标分别进行缩放。同样使用了 NumPy 的切片和广播机制来实现缩放操作。
if self.keypoints is not None:
# 将关键点的所有 x 坐标乘以 scale_w 。
self.keypoints[..., 0] *= scale_w
# 将关键点的所有 y 坐标乘以 scale_h 。
self.keypoints[..., 1] *= scale_h
# 这个 scale 方法的主要作用是对 Instances 实例中的边界框、分割掩码和关键点进行缩放。通过传入宽度和高度的缩放比例,它可以灵活地调整实例中所有相关数据的尺寸。如果只需要缩放边界框,可以通过设置 bbox_only 参数来简化操作。
# 这段代码是 Instances 类中的 denormalize 方法,用于将实例中的边界框、分割掩码和关键点的坐标从归一化状态转换为绝对坐标。
# 定义 denormalize 方法,接受两个参数。
# 1.w :图像的宽度。
# 2.h :图像的高度。
def denormalize(self, w, h):
# 从标准化坐标中对框、段和关键点进行非标准化处理。
"""Denormalizes boxes, segments, and keypoints from normalized coordinates."""
# 检查 self.normalized 是否为 True ,即是否处于归一化状态。
if not self.normalized:
# 如果 self.normalized 为 False ,则直接返回,不需要进行任何操作,因为数据已经处于绝对坐标状态。
return
# 调用 self._bboxes 对象的 mul 方法来缩放边界框。 self._bboxes 是一个 Bboxes 类的实例,其 mul 方法负责将边界框的每个坐标按指定的比例缩放。传入的 scale 参数是一个元组 (w, h, w, h) ,分别对应边界框的四个坐标(x1, y1, x2, y2)的缩放比例,即将归一化的坐标转换为绝对坐标。
self._bboxes.mul(scale=(w, h, w, h))
# 对分割掩码的 x 坐标和 y 坐标分别进行缩放。这里使用了 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 0 和 1 分别表示 x 和 y 坐标。
# 将分割掩码的所有 x 坐标乘以 w ,将其从归一化状态转换为绝对坐标。
self.segments[..., 0] *= w
# 将分割掩码的所有 y 坐标乘以 h ,将其从归一化状态转换为绝对坐标。
self.segments[..., 1] *= h
# 如果关键点数据存在,则对关键点的 x 坐标和 y 坐标分别进行缩放。同样使用了 NumPy 的切片和广播机制来实现缩放操作。
if self.keypoints is not None:
# 将关键点的所有 x 坐标乘以 w ,将其从归一化状态转换为绝对坐标。
self.keypoints[..., 0] *= w
# 将关键点的所有 y 坐标乘以 h ,将其从归一化状态转换为绝对坐标。
self.keypoints[..., 1] *= h
# 将 self.normalized 设置为 False ,表示数据已经转换为绝对坐标状态。
self.normalized = False
# 这个 denormalize 方法的主要作用是将 Instances 实例中的边界框、分割掩码和关键点的坐标从归一化状态转换为绝对坐标状态。通过传入图像的宽度和高度,它可以灵活地调整实例中所有相关数据的坐标,使其与图像的实际尺寸相对应。
# 这段代码是 Instances 类中的 normalize 方法,用于将实例中的边界框、分割掩码和关键点的坐标从绝对坐标状态转换为归一化状态。
# 定义 normalize 方法,接受两个参数。
# 1.w :图像的宽度。
# 2.h :图像的高度。
def normalize(self, w, h):
# 将边界框、线段和关键点标准化为图像尺寸。
"""Normalize bounding boxes, segments, and keypoints to image dimensions."""
# 检查 self.normalized 是否为 True ,即是否已经处于归一化状态。 如果 self.normalized 为 True ,则直接返回,不需要进行任何操作,因为数据已经处于归一化状态。
if self.normalized:
return
# 调用 self._bboxes 对象的 mul 方法来缩放边界框。 self._bboxes 是一个 Bboxes 类的实例,其 mul 方法负责将边界框的每个坐标按指定的比例缩放。传入的 scale 参数是一个元组 (1 / w, 1 / h, 1 / w, 1 / h) ,分别对应边界框的四个坐标(x1, y1, x2, y2)的缩放比例,即将绝对坐标转换为归一化坐标。
self._bboxes.mul(scale=(1 / w, 1 / h, 1 / w, 1 / h))
# 对分割掩码的 x 坐标和 y 坐标分别进行缩放。这里使用了 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 0 和 1 分别表示 x 和 y 坐标。
# 将分割掩码的所有 x 坐标除以 w ,将其从绝对坐标状态转换为归一化状态。
self.segments[..., 0] /= w
# 将分割掩码的所有 y 坐标除以 h ,将其从绝对坐标状态转换为归一化状态。
self.segments[..., 1] /= h
# 如果关键点数据存在,则对关键点的 x 坐标和 y 坐标分别进行缩放。同样使用了 NumPy 的切片和广播机制来实现缩放操作。
if self.keypoints is not None:
# 将关键点的所有 x 坐标除以 w ,将其从绝对坐标状态转换为归一化状态。
self.keypoints[..., 0] /= w
# 将关键点的所有 y 坐标除以 h ,将其从绝对坐标状态转换为归一化状态。
self.keypoints[..., 1] /= h
# 将 self.normalized 设置为 True ,表示数据已经转换为归一化状态。
self.normalized = True
# 这个 normalize 方法的主要作用是将 Instances 实例中的边界框、分割掩码和关键点的坐标从绝对坐标状态转换为归一化状态。通过传入图像的宽度和高度,它可以灵活地调整实例中所有相关数据的坐标,使其与图像的相对尺寸相对应。
# 这段代码是 Instances 类中的 add_padding 方法,用于在实例中的边界框、分割掩码和关键点的坐标上添加填充偏移。
# 定义 add_padding 方法,接受两个参数。
# 1.padw :宽度方向的填充偏移。
# 2.padh :高度方向的填充偏移。
def add_padding(self, padw, padh):
# 处理矩形和马赛克情况。
"""Handle rect and mosaic situation."""
# 断言检查 self.normalized 是否为 False ,即是否处于绝对坐标状态。如果 self.normalized 为 True ,则抛出异常,提示应在绝对坐标状态下添加填充偏移,因为归一化坐标不适用于直接添加像素级偏移。
assert not self.normalized, "you should add padding with absolute coordinates." # 您应该添加具有绝对坐标的填充。
# 调用 self._bboxes 对象的 add 方法来添加边界框的偏移。 self._bboxes 是一个 Bboxes 类的实例,其 add 方法负责将边界框的每个坐标按指定的偏移量进行调整。传入的 offset 参数是一个元组 (padw, padh, padw, padh) ,分别对应边界框的四个坐标(x1, y1, x2, y2)的偏移量。
self._bboxes.add(offset=(padw, padh, padw, padh))
# 对分割掩码的 x 坐标和 y 坐标分别添加偏移。这里使用了 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 0 和 1 分别表示 x 和 y 坐标。
# 将分割掩码的所有 x 坐标加上 padw ,使其在宽度方向上增加偏移。
self.segments[..., 0] += padw
# 将分割掩码的所有 y 坐标加上 padh ,使其在高度方向上增加偏移。
self.segments[..., 1] += padh
# 如果关键点数据存在,则对关键点的 x 坐标和 y 坐标分别添加偏移。同样使用了 NumPy 的切片和广播机制来实现偏移操作。
if self.keypoints is not None:
# 将关键点的所有 x 坐标加上 padw ,使其在宽度方向上增加偏移。
self.keypoints[..., 0] += padw
# 将关键点的所有 y 坐标加上 padh ,使其在高度方向上增加偏移。
self.keypoints[..., 1] += padh
# 这个 add_padding 方法的主要作用是在 Instances 实例中的边界框、分割掩码和关键点的坐标上添加指定的填充偏移。通过这种方式,可以调整实例中所有相关数据的位置,以适应图像的填充操作,确保数据的坐标与图像的实际布局一致。
# 这段代码是 Instances 类中的 __getitem__ 方法,用于实现对实例的索引操作,允许用户通过索引来获取部分实例数据。
# 定义 __getitem__ 方法,接受一个参数。
# 1.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.
"""
# 根据 index 获取分割掩码的子集。如果 self.segments 不为空,则使用 self.segments[index] 获取索引对应的分割掩码子集。如果 self.segments 为空,则直接返回 self.segments (即空的分割掩码).
segments = self.segments[index] if len(self.segments) else self.segments
# 根据 index 获取关键点的子集。如果 self.keypoints 不为 None ,则使用 self.keypoints[index] 获取索引对应的关键点子集。如果 self.keypoints 为 None ,则返回 None ,表示没有关键点数据。
keypoints = self.keypoints[index] if self.keypoints is not None else None
# 根据 index 获取边界框的子集。使用 self.bboxes[index] 获取索引对应的边界框子集。
bboxes = self.bboxes[index]
# 获取当前边界框的格式,存储在 bbox_format 中。
bbox_format = self._bboxes.format
# 使用获取到的边界框、分割掩码、关键点、边界框格式和归一化状态创建一个新的 Instances 实例。 返回这个新的 Instances 实例,它包含了索引对应的实例数据。
return Instances(
bboxes=bboxes,
segments=segments,
keypoints=keypoints,
bbox_format=bbox_format,
normalized=self.normalized,
)
# 这个 __getitem__ 方法的主要作用是实现对 Instances 实例的索引操作,允许用户通过索引来获取部分实例数据。它能够处理边界框、分割掩码和关键点的索引,并返回一个新的 Instances 实例,包含索引对应的子集数据。这种方法使得对实例数据的访问更加灵活和方便。
# 这段代码是 Instances 类中的 flipud 方法,用于对实例中的边界框、分割掩码和关键点进行上下翻转(垂直翻转)。
# 定义 flipud 方法,接受一个参数。这个方法用于将实例中的所有数据在垂直方向上进行翻转。
# 1.h :表示图像的高度。
def flipud(self, h):
# 垂直翻转边界框、线段和关键点的坐标。
"""Flips the coordinates of bounding boxes, segments, and keypoints vertically."""
# 根据边界框的格式进行翻转操作。
# 如果边界框格式是 "xyxy" (左上角和右下角坐标),则需要交换 y1 和 y2 的值。
if self._bboxes.format == "xyxy":
# 复制边界框的 y1 坐标。
y1 = self.bboxes[:, 1].copy()
# 复制边界框的 y2 坐标。
y2 = self.bboxes[:, 3].copy()
# 将 y1 更新为 h - y2 ,即新的 y1 坐标。
self.bboxes[:, 1] = h - y2
# 将 y2 更新为 h - y1 ,即新的 y2 坐标。
self.bboxes[:, 3] = h - y1
# 如果边界框格式不是 "xyxy" ,则直接将 y 坐标翻转为 h - y 。
else:
# 将 y 坐标更新为 h - y ,实现垂直翻转。
self.bboxes[:, 1] = h - self.bboxes[:, 1]
# 对分割掩码的 y 坐标进行翻转。将分割掩码的所有 y 坐标更新为 h - y ,实现垂直翻转。 使用 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 1 表示 y 坐标。
self.segments[..., 1] = h - self.segments[..., 1]
# 如果关键点数据存在,则对关键点的 y 坐标进行翻转。
if self.keypoints is not None:
# 将关键点的所有 y 坐标更新为 h - y ,实现垂直翻转。同样使用 NumPy 的切片和广播机制来实现翻转操作。
self.keypoints[..., 1] = h - self.keypoints[..., 1]
# 这个 flipud 方法的主要作用是对 Instances 实例中的边界框、分割掩码和关键点进行上下翻转。通过传入图像的高度 h ,它可以灵活地调整实例中所有相关数据的位置,使其在垂直方向上进行翻转,这在图像处理和数据增强等任务中非常有用。
# 这段代码是 Instances 类中的 fliplr 方法,用于对实例中的边界框、分割掩码和关键点进行左右翻转(水平翻转)。
# 定义 fliplr 方法,接受一个参数。这个方法用于将实例中的所有数据在水平方向上进行翻转。
# 1.w :表示图像的宽度。
def fliplr(self, w):
# 水平反转边界框和线段的顺序。
"""Reverses the order of the bounding boxes and segments horizontally."""
# 根据边界框的格式进行翻转操作。
# 如果边界框格式是 "xyxy" (左上角和右下角坐标),则需要交换 x1 和 x2 的值。
if self._bboxes.format == "xyxy":
# 复制边界框的 x1 坐标。
x1 = self.bboxes[:, 0].copy()
# 复制边界框的 x2 坐标。
x2 = self.bboxes[:, 2].copy()
# 将 x1 更新为 w - x2 ,即新的 x1 坐标。
self.bboxes[:, 0] = w - x2
# 将 x2 更新为 w - x1 ,即新的 x2 坐标。
self.bboxes[:, 2] = w - x1
# 如果边界框格式不是 "xyxy" ,则直接将 x 坐标翻转为 w - x 。
else:
# 将 x 坐标更新为 w - x ,实现水平翻转。
self.bboxes[:, 0] = w - self.bboxes[:, 0]
# 对分割掩码的 x 坐标进行翻转。 self.segments[..., 0] = w - self.segments[..., 0] :将分割掩码的所有 x 坐标更新为 w - x ,实现水平翻转。使用 NumPy 的切片和广播机制, ... 表示对所有维度进行操作, 0 表示 x 坐标。
self.segments[..., 0] = w - self.segments[..., 0]
# 如果关键点数据存在,则对关键点的 x 坐标进行翻转。
if self.keypoints is not None:
# 将关键点的所有 x 坐标更新为 w - x ,实现水平翻转。同样使用 NumPy 的切片和广播机制来实现翻转操作。
self.keypoints[..., 0] = w - self.keypoints[..., 0]
# 这个 fliplr 方法的主要作用是对 Instances 实例中的边界框、分割掩码和关键点进行左右翻转。通过传入图像的宽度 w ,它可以灵活地调整实例中所有相关数据的位置,使其在水平方向上进行翻转,这在图像处理和数据增强等任务中非常有用。
# 这段代码是 Instances 类中的 clip 方法,用于将实例中的边界框、分割掩码和关键点的坐标裁剪到指定的范围内,以确保它们不会超出图像的边界。
# 定义 clip 方法,接受两个参数。
# 1.w :图像的宽度。
# 2.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")
# 对边界框的 x 坐标和 y 坐标分别进行裁剪。使用 NumPy 的 clip 方法来实现裁剪操作,确保坐标不会超出图像的边界。
# 将 x 坐标( x1 和 x2 )裁剪到 [0, w] 范围内。
self.bboxes[:, [0, 2]] = self.bboxes[:, [0, 2]].clip(0, w)
# 将 y 坐标( y1 和 y2 )裁剪到 [0, h] 范围内。
self.bboxes[:, [1, 3]] = self.bboxes[:, [1, 3]].clip(0, h)
# 如果原始边界框格式不是 "xyxy" ,则将边界框格式恢复到原始格式。这一步确保裁剪操作不会改变边界框的原始格式。
if ori_format != "xyxy":
self.convert_bbox(format=ori_format)
# 对分割掩码的 x 坐标和 y 坐标分别进行裁剪。使用 NumPy 的 clip 方法来实现裁剪操作,确保坐标不会超出图像的边界。
# 将 x 坐标裁剪到 [0, w] 范围内。
self.segments[..., 0] = self.segments[..., 0].clip(0, w)
# 将 y 坐标裁剪到 [0, h] 范围内。
self.segments[..., 1] = self.segments[..., 1].clip(0, h)
# 如果关键点数据存在,则对关键点的 x 坐标和 y 坐标分别进行裁剪。使用 NumPy 的 clip 方法来实现裁剪操作,确保坐标不会超出图像的边界。
if self.keypoints is not None:
# 将 x 坐标裁剪到 [0, w] 范围内。
self.keypoints[..., 0] = self.keypoints[..., 0].clip(0, w)
# 将 y 坐标裁剪到 [0, h] 范围内。
self.keypoints[..., 1] = self.keypoints[..., 1].clip(0, h)
# 这个 clip 方法的主要作用是确保 Instances 实例中的边界框、分割掩码和关键点的坐标不会超出图像的有效范围。通过传入图像的宽度和高度,它对所有相关数据进行裁剪,使其保持在 [0, w] 和 [0, h] 的范围内,从而避免坐标越界的问题。
# 这段代码是 Instances 类中的 remove_zero_area_boxes 方法,用于移除面积为零的边界框及其对应的分割掩码和关键点。
# 定义 remove_zero_area_boxes 方法,该方法不接受额外的参数。
def remove_zero_area_boxes(self):
# 删除零面积框,即剪切后某些框的宽度或高度可能为零。
# 这会删除它们。
"""
Remove zero-area boxes, i.e. after clipping some boxes may have zero width or height.
This removes them.
"""
# 计算边界框的面积,并生成一个布尔数组 good ,其中 True 表示边界框的面积大于零, False 表示面积为零。 self.bbox_areas 是一个属性,返回边界框的面积数组,然后通过比较操作 > 0 生成布尔数组。
good = self.bbox_areas > 0
# 检查布尔数组 good 中是否所有元素都是 True ,即是否所有边界框的面积都大于零。如果 not all(good) 为 True ,则表示存在面积为零的边界框,需要进行移除操作。
if not all(good):
# 使用布尔索引 good 对 self._bboxes 进行筛选,移除面积为零的边界框。 self._bboxes[good] 返回一个只包含面积大于零的边界框的新 Bboxes 实例。
self._bboxes = self._bboxes[good]
# 如果分割掩码存在且不为空,则使用布尔索引 good 对 self.segments 进行筛选,移除对应于面积为零的边界框的分割掩码。 self.segments[good] 返回一个只包含有效边界框对应的分割掩码的新数组。
if len(self.segments):
self.segments = self.segments[good]
# 如果关键点数据存在,则使用布尔索引 good 对 self.keypoints 进行筛选,移除对应于面积为零的边界框的关键点。 self.keypoints[good] 返回一个只包含有效边界框对应的关键点的新数组。
if self.keypoints is not None:
self.keypoints = self.keypoints[good]
# 返回布尔数组 good ,表示哪些边界框的面积大于零。这可以用于进一步的处理或检查。
return good
# 这个 remove_zero_area_boxes 方法的主要作用是移除 Instances 实例中面积为零的边界框及其对应的分割掩码和关键点。通过生成布尔数组并使用布尔索引,它能够有效地筛选出有效的边界框及相关数据,确保实例中不包含无效的边界框信息。
# 这段代码是 Instances 类中的 update 方法,用于更新实例中的边界框、分割掩码和关键点数据。
# 定义 update 方法,接受三个参数。
# 1.bboxes :新的边界框数据。
# 2.segments :新的分割掩码数据,默认为 None。
# 3.keypoints :新的关键点数据,默认为 None 。
def update(self, bboxes, segments=None, keypoints=None):
# 更新实例变量。
"""Updates instance variables."""
# 使用新的边界框数据 bboxes 创建一个新的 Bboxes 实例,并将其存储在 self._bboxes 中。 format=self._bboxes.format 确保新创建的 Bboxes 实例使用与当前实例相同的边界框格式。这一步更新了实例中的边界框数据,并保留了原有的格式设置。
self._bboxes = Bboxes(bboxes, format=self._bboxes.format)
# 如果 segments 不为 None ,则更新 self.segments 为新的分割掩码数据 segments 。这一步仅在提供了新的分割掩码数据时进行更新,否则保留原有的分割掩码数据。
if segments is not None:
self.segments = segments
# 如果 keypoints 不为 None ,则更新 self.keypoints 为新的关键点数据 keypoints 。这一步仅在提供了新的关键点数据时进行更新,否则保留原有的关键点数据。
if keypoints is not None:
self.keypoints = keypoints
# 这个 update 方法的主要作用是更新 Instances 实例中的边界框、分割掩码和关键点数据。它允许用户传入新的数据来替换实例中的相应部分,同时保留原有的格式设置和其他数据不变. 这种更新机制使得实例能够灵活地适应新的数据输入,适用于动态数据处理和更新的场景。
# 这段代码是 Instances 类中的 __len__ 方法,用于返回实例中边界框的数量。
# 定义 __len__ 方法。这是一个特殊的方法,用于实现 Python 的内置函数 len() 。当调用 len(instance) 时,Python 会自动调用该实例的 __len__ 方法。
def __len__(self):
# 返回实例列表的长度。
"""Return the length of the instance list."""
# 返回 self.bboxes 的长度,即边界框的数量。 self.bboxes 是一个二维数组,其中每一行表示一个边界框的坐标。 len(self.bboxes) 返回该数组的行数,也就是边界框的数量。
return len(self.bboxes)
# 通过实现 __len__ 方法, Instances 类的对象可以使用内置的 len() 函数来获取边界框的数量。这使得代码更加简洁和直观,符合 Python 的习惯用法。例如,如果 instances 是一个 Instances 实例,那么 len(instances) 将返回边界框的数量。
# 这段代码是 Instances 类中的一个类方法 concatenate ,用于将多个 Instances 实例合并为一个新的 Instances 实例。
# @classmethod
# 在Python中, @classmethod 是一个装饰器,用于将一个普通的方法转换为类方法。类方法的第一个参数总是 cls ,它代表类本身,而不是类的实例。这意味着你可以在不创建类实例的情况下调用这个方法,并且可以在这个方法内部访问类的属性和方法。
# 类方法通常用于不需要类实例就可以执行的操作,例如 :
# 创建类实例时不依赖于类的状态。
# 需要访问类属性而不是实例属性。
# 实现备选构造器(alternative constructors)。
# 类方法可以被子类继承,并且可以被子类的实例和子类本身调用。
# 使用 @classmethod 装饰器定义一个类方法 concatenate 。
@classmethod
# 定义 concatenate 方法,接受三个参数。
# 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 是否为列表或元组。如果不是,则抛出异常,提示 instances_list 的类型不正确。
assert isinstance(instances_list, (list, tuple))
# 如果 instances_list 为空,则返回一个新的 Instances 实例,其中边界框数组为空。
if not instances_list:
# np.empty(0) 创建一个空的 NumPy 数组。 cls(np.empty(0)) 使用类的构造函数创建一个新的 Instances 实例,传入空的边界框数组。
return cls(np.empty(0))
# 断言检查 instances_list 中的每个元素是否都是 Instances 实例。如果有任何元素不是 Instances 实例,则抛出异常。
assert all(isinstance(instance, Instances) for instance in instances_list)
# 如果 instances_list 只有一个元素,则直接返回该元素,因为不需要合并。
if len(instances_list) == 1:
return instances_list[0]
# 获取合并所需的参数。
# 判断是否有关键点数据。
use_keypoint = instances_list[0].keypoints is not None
# 获取边界框的格式。
bbox_format = instances_list[0]._bboxes.format
# 获取归一化状态。
normalized = instances_list[0].normalized
# 使用 np.concatenate 将 instances_list 中所有 Instances 实例的边界框、分割掩码和关键点数组按指定轴 axis 合并。 cat_boxes 、 cat_segments 和 cat_keypoints 分别存储合并后的边界框、分割掩码和关键点数组。
# [ins.bboxes for ins in instances_list] :提取每个实例的边界框数组。
cat_boxes = np.concatenate([ins.bboxes for ins in instances_list], axis=axis)
# [b.segments for b in instances_list] :提取每个实例的分割掩码数组。
cat_segments = np.concatenate([b.segments for b in instances_list], axis=axis)
# [b.keypoints for b in instances_list] :提取每个实例的关键点数组(如果存在)。
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)
# 这个 concatenate 类方法的主要作用是将多个 Instances 实例合并为一个新的 Instances 实例。它首先进行参数验证,确保输入的列表不为空且所有元素都是 Instances 实例。然后根据列表的长度决定是否需要合并,最后使用 np.concatenate 进行合并并返回新的实例。这种方法方便地实现了实例数据的批量处理和合并,适用于需要对多个实例进行统一操作的场景。
# 这段代码是 Instances 类中的一个属性方法 bboxes ,用于获取实例中的边界框数据。
# property(fget=None, fset=None, fdel=None, doc=None)
# 在 Python 中, property 是一个内置函数,用于创建属性访问器(getter)、修改器(setter)和删除器(deleter)。 property 函数通常用于将类的方法转换为属性,使得这些方法可以像访问普通属性一样被访问。
# 参数 :
# fget :一个函数,用于获取属性值。如果没有提供,则属性将只写。
# fset :一个函数,用于设置属性值。如果没有提供,则属性将只读。
# fdel :一个函数,用于删除属性。如果没有提供,则属性将不能被删除。
# doc :属性的文档字符串。
# 返回值 :
# 返回一个属性对象,该对象可以用于定义管理属性访问的方法。
# 用法示例 :
# @property
# property 函数是 Python 中实现 getter/setter/ deleter 的标准方式,它允许在访问属性时执行额外的逻辑,例如验证或更新其他属性。
# 使用 @property 装饰器定义一个属性方法 bboxes 。这意味着可以像访问实例变量一样访问 bboxes ,而不需要调用方法。
@property
# bboxes 属性用于返回实例中的边界框数据。
def bboxes(self):
# 返回边界框。
"""Return bounding boxes."""
# 返回 self._bboxes 对象的 bboxes 属性,即边界框数据。
# self._bboxes 是一个 Bboxes 类的实例,其 bboxes 属性存储了边界框的坐标数组。
# 通过这种方式, bboxes 属性提供了一个方便的接口来访问和使用实例中的边界框数据。
return self._bboxes.bboxes
# 这个 bboxes 属性方法的主要作用是提供一个简洁的方式来获取 Instances 实例中的边界框数据。通过访问 instance.bboxes ,用户可以直接获取到边界框的坐标数组,而不需要直接操作 Bboxes 对象的内部属性. 这种设计使得代码更加清晰和易于使用。
# 这个 Instances 类提供了一个灵活的方式来表示和操作图像实例的边界框、分割掩码和关键点。它支持多种操作,如格式转换、缩放、翻转、裁剪和合并等,使得图像处理和目标检测等任务更加方便和高效。