YOLOv8-ultralytics-8.2.103部分代码阅读笔记-loaders.py
loaders.py
ultralytics\data\loaders.py
目录
9.def get_best_youtube_url(url, method="pytube"):
1.所需的库和模块
# Ultralytics YOLO 🚀, AGPL-3.0 license
import glob
import math
import os
import time
from dataclasses import dataclass
from pathlib import Path
from threading import Thread
from urllib.parse import urlparse
import cv2
import numpy as np
import requests
import torch
from PIL import Image
from ultralytics.data.utils import FORMATS_HELP_MSG, IMG_FORMATS, VID_FORMATS
from ultralytics.utils import IS_COLAB, IS_KAGGLE, LOGGER, ops
from ultralytics.utils.checks import check_requirements
2.class SourceTypes:
# 这段代码是一个使用 Python 的 dataclasses 模块定义的类,名为 SourceTypes 。
# @dataclass
# @dataclass 是 Python 中的一个装饰器,它来自于 dataclasses 模块,用于自动添加特殊方法到类定义中,使得定义类更加简洁和方便。当你使用 @dataclass 装饰一个类时,Python 会自动为该类生成以下内容 :
# __init__ 方法 :一个初始化方法,用于创建类的实例并设置其属性。
# __repr__ 方法 :一个表示方法,用于返回类的字符串表示,通常用于调试。
# __eq__ 方法 :一个等价性比较方法,用于比较两个类的实例是否相等。
# 其他可选方法 :如 __lt__ 、 __le__ 、 __gt__ 、 __ge__ 和 __hash__ ,这些方法只有在指定了 order=True 参数时才会被自动添加。
# @dataclass 还接受一些参数来自定义生成的行为 :
# init :一个布尔值,默认为 True 。如果设置为 False ,则不会生成 __init__ 方法。
# repr :一个布尔值,默认为 True 。如果设置为 False ,则不会生成 __repr__ 方法。
# eq :一个布尔值,默认为 True 。如果设置为 False ,则不会生成 __eq__ 和 __hash__ 方法。
# order :一个布尔值,默认为 False 。如果设置为 True ,则会生成 __lt__ 、 __le__ 、 __gt__ 、 __ge__ 方法。
# frozen :一个布尔值,默认为 False 。如果设置为 True ,则生成的类实例将被冻结,不能修改属性。
# 使用这些参数,可以根据需要定制 @dataclass 的行为。
# dataclasses 是 Python 3.7 引入的一个模块,用于自动添加特殊方法,如 __init__() 和 __repr__() ,到用户定义的类中,使得定义类更加简洁。
@dataclass
# SourceTypes 类用于表示预测输入的不同来源类型。
class SourceTypes:
# 用于表示各种类型预测的输入源的类。
"""Class to represent various types of input sources for predictions."""
# 它有四个属性,每个属性都是一个布尔值( bool ),默认值都设置为 False。
# stream : 表示输入是否来自流数据。
stream: bool = False
# screenshot : 表示输入是否来自屏幕截图。
screenshot: bool = False
# from_img : 表示输入是否来自图像文件。
from_img: bool = False
# tensor : 表示输入是否来自张量数据。
tensor: bool = False
# 使用 @dataclass 装饰器,Python 会自动为这个类生成初始化方法 __init__ ,以及一些其他的魔术方法,比如 __repr__ 和 __eq__ 。
# 这意味着你可以直接创建 SourceTypes 类的实例,并根据需要设置这些属性 :
# source = SourceTypes(stream=True, screenshot=False, from_img=False, tensor=False)
# 这将创建一个 SourceTypes 对象,其中 stream 属性被设置为 True ,其他属性保持默认值 False 。这个类可以用于配置预测模型的输入源,或者在需要区分不同输入类型的场景中使用。
3.class LoadStreams:
# 这段代码定义了一个名为 LoadStreams 的类,它用于加载和处理视频流。
class LoadStreams:
# 适用于各种类型视频流的流加载器,支持 RTSP、RTMP、HTTP 和 TCP 流。
# 方法:
# __init__ :初始化流加载器。
# update :在守护线程中读取流帧。
# close :关闭流加载器并释放资源。
# __iter__ :返回类的迭代器对象。
# __next__ :返回源路径、转换后的图像和原始图像以供处理。
# __len__ :返回源对象的长度。
"""
Stream Loader for various types of video streams, Supports RTSP, RTMP, HTTP, and TCP streams.
Attributes:
sources (str): The source input paths or URLs for the video streams.
vid_stride (int): Video frame-rate stride, defaults to 1.
buffer (bool): Whether to buffer input streams, defaults to False.
running (bool): Flag to indicate if the streaming thread is running.
mode (str): Set to 'stream' indicating real-time capture.
imgs (list): List of image frames for each stream.
fps (list): List of FPS for each stream.
frames (list): List of total frames for each stream.
threads (list): List of threads for each stream.
shape (list): List of shapes for each stream.
caps (list): List of cv2.VideoCapture objects for each stream.
bs (int): Batch size for processing.
Methods:
__init__: Initialize the stream loader.
update: Read stream frames in daemon thread.
close: Close stream loader and release resources.
__iter__: Returns an iterator object for the class.
__next__: Returns source paths, transformed, and original images for processing.
__len__: Return the length of the sources object.
Example:
```bash
yolo predict source='rtsp://example.com/media.mp4'
```
"""
# 初始化方法 ( __init__ )。
# 1.sources : 视频流的来源,可以是文件路径或视频URL。
# 2.vid_stride : 视频帧率步长。
# 3.buffer : 是否缓冲输入流。
def __init__(self, sources="file.streams", vid_stride=1, buffer=False):
# 初始化实例变量并检查一致的输入流形状。
"""Initialize instance variables and check for consistent input stream shapes."""
# 类变量。
# torch.backends.cudnn.benchmark
# cudnn.benchmark 是 PyTorch 中的一个设置,用于控制 NVIDIA 的 cuDNN 库是否在程序运行时自动为每个卷积层选择最优的算法。这个设置可以影响程序的性能,尤其是在深度学习模型中使用卷积层时。
# 定义和用法 :
# torch.backends.cudnn.benchmark :这是一个布尔值设置,可以设置为 True 或 False 。
# True :开启 cuDNN 的基准测试模式。在这个模式下,cuDNN 会在程序开始运行时为每个卷积层自动选择最优的算法。这可能会在程序启动时增加一些额外的时间开销,因为 cuDNN 需要对不同的算法进行基准测试,但一旦选择了最优算法,后续的卷积操作将会更快。
# False :关闭基准测试模式。cuDNN 将使用默认的卷积算法,这可能不是最优的选择,但适用于模型输入尺寸在运行过程中会改变的情况。
# 适用场景 :
# 固定输入尺寸 :如果你的模型输入尺寸(例如,图像尺寸和批处理大小)是固定的,设置 torch.backends.cudnn.benchmark = True 可以提高运行效率,因为 cuDNN 可以预先选择最优算法。
# 变化输入尺寸 :如果输入尺寸可能发生变化,开启 benchmark 可能导致性能下降,因为每次输入尺寸改变时,cuDNN 都可能重新搜索算法。
# 注意事项 :
# 性能影响 :开启 cudnn.benchmark 可能会在程序启动时增加一些额外的时间开销,但可以提高后续卷积操作的速度。
# 结果可重复性 :开启 cudnn.benchmark 可能会导致结果的轻微变化,因为 cuDNN 可能会选择不同的算法。如果需要确保结果的完全可重复性,可能需要关闭 cudnn.benchmark 并设置 torch.backends.cudnn.deterministic = True 。
# 总的来说, cudnn.benchmark 是一个有用的设置,可以帮助优化深度学习模型的性能,但需要根据具体的应用场景和需求来决定是否开启。
# 设置为 True 以提高固定大小推理的速度。
torch.backends.cudnn.benchmark = True # faster for fixed-size inference
# 用于缓冲输入流。
self.buffer = buffer # buffer input streams
# 运行标志,用于线程控制。
self.running = True # running flag for Thread
# 设置为 "stream",表示处理流。
self.mode = "stream"
# 视频帧率步长。
self.vid_stride = vid_stride # video frame-rate stride
# 如果 sources 是一个文件路径,并且该文件存在( os.path.isfile(sources) 返回 True ),则使用 Path 对象来读取文件内容。 Path 是 pathlib 模块中的一个类,用于处理文件路径。
# read_text() 方法用于读取文件内容,并返回一个字符串。 rsplit() 方法用于从字符串的右侧开始分割字符串,这在处理文件路径时非常有用,特别是当文件包含多个路径时。
# 如果 sources 不是一个文件路径,或者文件不存在,则直接将 sources 放入一个列表中。这样,无论是单个路径还是文件中的多个路径,都会被存储在一个列表中。
sources = Path(sources).read_text().rsplit() if os.path.isfile(sources) else [sources]
# 计算 sources 列表的长度,即视频流的数量,并将其存储在变量 n 中。
n = len(sources)
# 视频流的数量。
self.bs = n
# 每个视频流的帧率。
self.fps = [0] * n # frames per second
# 每个视频流的帧数。
self.frames = [0] * n
# 用于读取帧的线程列表。
self.threads = [None] * n
# 视频捕获对象列表。
self.caps = [None] * n # video capture objects
# 存储每个视频流的图像列表。
self.imgs = [[] for _ in range(n)] # images
# 存储每个视频流图像的形状。
self.shape = [[] for _ in range(n)] # image shapes
# 清理后的源名称列表。
# def clean_str(s): -> 用于清理字符串中的特定字符,将它们替换为下划线 _ 。返回替换后的字符串。 -> return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
self.sources = [ops.clean_str(x) for x in sources] # clean source names for later
# 这段代码是 LoadStreams 类中的一部分,它遍历 sources 列表中的每个视频流源,并进行相应的处理。
# 这行代码使用 enumerate 函数遍历 sources 列表, i 是索引(从0开始), s 是当前的源地址。
for i, s in enumerate(sources): # index, source
# Start thread to read frames from video stream
# 这行代码创建一个字符串 st ,用于记录当前处理的 视频流 序号 和 地址 ,后面会用它来记录 日志信息 。
st = f"{i + 1}/{n}: {s}... "
# parsed_url = urlparse(url)
# urlparse() 函数是 Python 的 urllib.parse 模块中的一个函数,用于将 URL 解析为几个组成部分。
# 解释 :
# scheme : URL 的协议部分(例如 http 、 https )。
# netloc : URL 的网络位置部分(包括域名和端口)。
# path : URL 的路径部分。
# params : URL 的参数部分(仅对某些 URL 协议有效)。
# query : URL 的查询字符串部分。
# fragment : URL 的片段部分(例如锚点)。
# urlparse() 函数返回一个 ParseResult 对象,该对象包含上述属性。这个对象类似于一个元组,但每个组件都可以像属性一样访问。
# 注意事项 :
# urlparse() 函数可以处理相对和绝对的 URL。
# 如果 URL 无效或格式不正确, urlparse() 可能会抛出异常或返回意外的解析结果。
# 通过这种方式, urlparse() 函数提供了一种方便的方法来解析和处理 URL,使其在网络编程和数据处理中非常有用。
# 这行代码检查当前源地址是否是YouTube视频。 urlparse 函数用于解析URL, hostname 属性返回URL的主机名。
if urlparse(s).hostname in {"www.youtube.com", "youtube.com", "youtu.be"}: # if source is YouTube video
# YouTube format i.e. 'https://www.youtube.com/watch?v=Jsn8D3aC840' or 'https://youtu.be/Jsn8D3aC840'
# 如果源地址是YouTube视频,调用 get_best_youtube_url 函数来获取最佳YouTube URL。
s = get_best_youtube_url(s)
# 这行代码检查源地址是否是数字。如果是数字(例如 '0' ),则使用 eval 函数将其转换为整数。这里 eval 函数的使用需要谨慎,因为它会执行字符串中的代码,可能会带来安全风险。
s = eval(s) if s.isnumeric() else s # i.e. s = '0' local webcam
# 这行代码检查如果源地址是0(表示本地摄像头),并且当前环境是Colab或Kaggle,抛出 NotImplementedError 异常。这是因为Colab和Kaggle不支持本地摄像头。
# IS_COLAB -> 检查当前脚本是否在 Google Colab 笔记本中运行。
# IS_KAGGLE -> 检查当前脚本是否在 Kaggle 内核中运行。
if s == 0 and (IS_COLAB or IS_KAGGLE):
raise NotImplementedError(
"'source=0' webcam not supported in Colab and Kaggle notebooks. " # Colab 和 Kaggle 笔记本不支持“source=0”网络摄像头。
"Try running 'source=0' in a local environment." # 尝试在本地环境中运行“source=0”。
)
# 这行代码使用 cv2.VideoCapture 创建一个视频捕获对象,用于读取视频流。 s 是视频流的地址,可以是文件路径、网络摄像头地址或YouTube视频链接。
self.caps[i] = cv2.VideoCapture(s) # store video capture object
# 这段代码的目的是为每个视频流源创建一个视频捕获对象,并处理一些特殊情况,如 YouTube 视频和本地摄像头。通过这种方式,可以统一处理不同类型的视频流源。
# 这段代码是 LoadStreams 类中用于检查视频流是否成功打开,并获取视频流的一些属性的逻辑。
# 检查视频流是否成功打开。
# self.caps[i].isOpened() : 这个方法检查视频捕获对象( cv2.VideoCapture 对象)是否成功打开视频流。
# 如果视频流没有成功打开,代码会抛出一个 ConnectionError 异常,并包含一个错误消息,指出无法打开的视频流地址。
if not self.caps[i].isOpened():
raise ConnectionError(f"{st}Failed to open {s}") # {st}无法打开{s}。
# 获取视频流的属性。
# 获取视频流的宽度,并将其转换为整数。
w = int(self.caps[i].get(cv2.CAP_PROP_FRAME_WIDTH))
# 获取视频流的高度,并将其转换为整数。
h = int(self.caps[i].get(cv2.CAP_PROP_FRAME_HEIGHT))
# 获取视频流的帧率。注意,这个值可能返回0或不是一个数字(NaN)。
fps = self.caps[i].get(cv2.CAP_PROP_FPS) # warning: may return 0 or nan
# 处理视频流的帧数。
# self.caps[i].get(cv2.CAP_PROP_FRAME_COUNT) : 获取视频流的总帧数。
# max(int(...), 0) : 确保帧数是一个非负整数。
# or float("inf") : 如果帧数为0或NaN,则设置为无穷大( float("inf") ),表示视频流是无限的。
self.frames[i] = max(int(self.caps[i].get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float(
"inf"
) # infinite stream fallback
# 处理视频流的帧率。
# math.isfinite(x)
# math.isfinite() 函数是 Python 的 math 模块中的一个函数,用于判断给定的数是否是有限的。一个数被认为是有限的,如果它不是无穷大( infinity )或不确定( NaN ,即“不是一个数字”)。
# 参数 :
# x :需要检查的数值,可以是整数、浮点数或者复数。
# 返回值 :
# 返回 True 如果 x 是有限的,即不是 infinity 也不是 NaN 。
# 返回 False 如果 x 是 infinity 或 NaN 。
# 注意事项 :
# math.isfinite() 函数在处理浮点数时特别有用,因为浮点运算可能会导致 infinity 或 NaN 的结果。
# 在 Python 3.8 及更高版本中, math.isfinite() 也可以接受复数作为参数,对于复数,如果实部和虚部都是有限的,则返回 True 。
# 在 Python 3.0 到 3.7 版本中, math.isfinite() 只接受实数作为参数,不接受复数。
# 这个函数在科学计算和数据处理中非常有用,因为它可以帮助识别和处理那些可能是由于计算错误而产生的非有限数值。
# math.isfinite(fps) : 检查帧率是否是一个有限的数字。
# fps if math.isfinite(fps) else 0 : 如果帧率不是有限数字,则将其设置为0。
# % 100 : 确保帧率是一个整数。
# max(..., 0) : 确保帧率是一个非负整数。
# or 30 : 如果帧率为0,则设置为30 FPS作为默认值。
self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30 # 30 FPS fallback
# 这段代码确保了视频流成功打开,并且能够获取视频流的基本属性,如宽度、高度、帧率和总帧数。如果某些属性无法获取或不合理,代码会使用合理的默认值或抛出异常。
# 这段代码是 LoadStreams 类中的一部分,它负责从每个视频流中读取第一帧图像,并启动一个线程来持续更新图像帧。
# 读取第一帧图像。
# self.caps[i].read() : 调用视频捕获对象的 read() 方法来读取一帧图像。
# success : 一个布尔值,表示是否成功读取帧。
# im : 读取到的图像帧,如果 success 为 True ,则 im 是一个有效的图像对象;如果 success 为 False ,则 im 是 None 。
success, im = self.caps[i].read() # guarantee first frame
# 检查读取结果。
# 如果 read() 方法没有成功读取帧( success 为 False )或者读取到的图像是 None ,则抛出 ConnectionError 异常,并包含一个错误消息,指出无法从视频流源 s 读取图像。
if not success or im is None:
raise ConnectionError(f"{st}Failed to read images from {s}") # {st}无法从 {s} 读取图像。
# 存储第一帧图像和其形状。
# 将读取到的第一帧图像添加到 self.imgs 列表的第 i 个元素中。
self.imgs[i].append(im)
# 获取图像的形状(高度、宽度、颜色通道数)并存储在 self.shape 列表的第 i 个元素中。
self.shape[i] = im.shape
# 创建并启动更新线程。
# Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
# 在 Python 中, Thread 是一个类,它属于 threading 模块,用于创建和管理线程。
# 参数 :
# group :这个参数已经被废弃,不需要使用。
# target :一个可调用的对象,它将在这个线程中执行。 target 函数必须接受一个参数,即 Thread 实例本身。
# name :线程的名称。如果未提供,则线程将没有名称。
# args :一个元组,包含传递给 target 函数的参数。
# kwargs :一个字典,包含传递给 target 函数的关键字参数。
# daemon :一个布尔值,表示线程是否作为守护线程运行。如果设置为 True ,则当主程序退出时,线程也会自动退出。
# 方法 :
# start() :启动线程。线程将在 target 函数中执行。
# run() :这是一个在 Thread 类中定义的方法,可以被重写。如果提供了 target 参数,则 run() 方法不会被直接调用,而是调用 target 。
# join(timeout=None) :等待线程终止。 timeout 参数是可选的,表示等待的秒数。
# is_alive() :返回线程是否仍然活跃。
# Thread 类是 Python 中实现多线程编程的基础,允许程序同时执行多个任务。
# self.threads[i] = Thread(...) : 创建一个新的线程对象,目标是 self.update 方法,参数是当前索引 i 、视频捕获对象 self.caps[i] 和视频流源 s 。
# daemon=True : 设置线程为守护线程,这意味着当主程序退出时,这个线程也会自动退出。
self.threads[i] = Thread(target=self.update, args=([i, self.caps[i], s]), daemon=True)
# 使用日志记录器记录成功读取第一帧图像的信息,包括视频流的序号、地址、帧数、形状和帧率。
LOGGER.info(f"{st}Success ✅ ({self.frames[i]} frames of shape {w}x{h} at {self.fps[i]:.2f} FPS)") # {self.frames[i]} 帧形状为 {w}x{h},FPS 为 {self.fps[i]:.2f}。
# 启动线程,开始持续更新图像帧。
self.threads[i].start()
# 日志记录新行。记录一个空的日志信息,用于在日志输出中创建一个新行,提高日志的可读性。
LOGGER.info("") # newline
# 这段代码确保了每个视频流都能成功读取第一帧图像,并为每个视频流创建一个线程来持续更新图像帧,这对于实时视频处理非常重要。
# 这段代码是一个视频流处理的框架,它能够处理多个视频流,包括本地文件、网络摄像头和YouTube视频。它使用多线程来并行处理视频流,以提高效率。
# 这段代码定义了一个名为 update 的方法,它是 LoadStreams 类的一部分。这个方法在一个守护线程中运行,用于持续读取视频流 i 的帧,并将其存储在缓冲区中。
# 方法签名。
# 1.self : 类实例的引用。
# 2.i : 视频流的索引。
# 3.cap : 视频捕获对象,用于读取视频流。
# 4.stream : 视频流的地址。
def update(self, i, cap, stream):
# 在守护线程中读取流“i”帧。
"""Read stream `i` frames in daemon thread."""
# 初始化变量。 n 已读取的帧数。 f 视频流的总帧数。
n, f = 0, self.frames[i] # frame number, frame array
# 循环条件。只要 self.running 标志为 True ,视频捕获对象打开,且已读取的帧数小于总帧数减一,循环就继续执行。
while self.running and cap.isOpened() and n < (f - 1):
# 缓冲区大小检查。如果当前缓冲区中的图像数量小于30,继续读取帧。
if len(self.imgs[i]) < 30: # keep a <=30-image buffer
# 读取帧。
# 增加帧计数。
n += 1
# 使用 cap.grab() 方法读取一帧,但不立即检索图像数据。
cap.grab() # .read() = .grab() followed by .retrieve()
# 帧率步长检查。 如果当前帧数是视频帧率步长的倍数,检索图像数据。
if n % self.vid_stride == 0:
# 检索图像。 success 一个布尔值,表示是否成功检索到图像。 im 检索到的图像。
success, im = cap.retrieve()
# 错误处理。
# 如果检索失败,创建一个空白图像,并记录警告日志。如果视频流无响应,尝试重新打开视频流。
if not success:
im = np.zeros(self.shape[i], dtype=np.uint8)
LOGGER.warning("WARNING ⚠️ Video stream unresponsive, please check your IP camera connection.") # 警告⚠️视频流无响应,请检查您的 IP 摄像机连接。
cap.open(stream) # re-open stream if signal was lost
# 缓冲区更新。
# 如果启用了缓冲区,将检索到的图像添加到缓冲区列表中。
if self.buffer:
self.imgs[i].append(im)
# 如果没有启用缓冲区,将缓冲区列表更新为只包含当前图像。
else:
self.imgs[i] = [im]
# 缓冲区满时等待。如果缓冲区已满,线程将休眠0.01秒,等待缓冲区变空。
else:
time.sleep(0.01) # wait until the buffer is empty
# 这个方法确保了视频流的持续读取,同时通过缓冲区控制内存使用,并在视频流无响应时尝试重新连接。这对于实时视频处理应用非常有用。
# 这段代码定义了一个名为 close 的方法,它是 LoadStreams 类的一部分。这个方法用于关闭视频流加载器并释放相关资源。
def close(self):
# 关闭流加载器并释放资源。
"""Close stream loader and release resources."""
# 停止运行标志。将 self.running 标志设置为 False ,这将通知所有线程停止运行。
self.running = False # stop flag for Thread
# 等待线程结束。
# 遍历 self.threads 列表中的所有线程。
for thread in self.threads:
# 如果线程仍在运行( is_alive() 返回 True ),则调用 thread.join() 方法等待线程结束,同时设置一个5秒的超时时间。
if thread.is_alive():
thread.join(timeout=5) # Add timeout
# 释放视频捕获对象。
# 遍历 self.caps 列表中的所有视频捕获对象。
for cap in self.caps: # Iterate through the stored VideoCapture objects
# 对于每个对象,尝试调用 cap.release() 方法来释放视频捕获资源。
try:
cap.release() # release video capture
# 如果在释放过程中发生异常,记录一条警告日志,指出无法释放视频捕获对象。
except Exception as e:
LOGGER.warning(f"WARNING ⚠️ Could not release VideoCapture object: {e}") # 警告 ⚠️ 无法释放 VideoCapture 对象:{e}。
# cv2.destroyAllWindows()
# cv2.destroyAllWindows() 是 OpenCV 库中的一个函数,用于关闭所有由 OpenCV 创建的窗口。这个函数是 OpenCV 2.x 和 3.x 版本中的 highgui 模块的一部分,用于图形用户界面(GUI)操作。
# 参数 :无参数。
# 返回值 :无返回值。
# 注意事项 :
# cv2.destroyAllWindows() 会关闭所有 OpenCV 创建的窗口,而不管它们是否在当前程序中创建。
# 如果你的应用程序中有多个窗口,调用 cv2.destroyAllWindows() 会关闭所有这些窗口。
# 在某些情况下,如果你只想关闭特定的窗口,可以使用 cv2.destroyWindow(windowName) ,其中 windowName 是窗口的名称。
# 这个函数在结束图像显示或在程序退出前清理资源时非常有用,确保不会留下任何悬挂的窗口。
# 关闭所有 OpenCV 窗口。调用 OpenCV 的 cv2.destroyAllWindows() 方法来关闭所有 OpenCV 创建的窗口。
cv2.destroyAllWindows()
# 这个方法确保了在不再需要视频流时,能够正确地释放所有资源,包括线程和视频捕获对象,并且关闭所有相关的窗口。这是一个很好的实践,可以避免资源泄露和程序崩溃。
# 这段代码定义了一个类的 __iter__ 方法,这是 Python 中实现迭代器协议的一部分。 __iter__ 方法允许一个对象成为迭代器,当该对象被用于 for 循环或者需要迭代时,这个方法会被调用。
# 方法签名。 self : 类实例的引用。
def __iter__(self):
# 遍历 YOLO 图像源并重新打开无响应的流。
"""Iterates through YOLO image feed and re-opens unresponsive streams."""
# 初始化一个计数器 count ,这里设置为 -1 可能是为了在下一个方法(如 __next__ )中递增计数器,使其从 0 开始。
self.count = -1
# 返回对象本身,这是实现迭代器协议的关键部分。这意味着对象 既是 迭代器 也是 被迭代的对象 (即,对象可以自我迭代)。
return self
# 在 __iter__ 方法中初始化 self.count 为 -1 是为了在 __next__ 方法中方便地从 0 开始计数。 实现迭代器时,确保 __next__ 方法在没有更多元素时能够抛出 StopIteration 异常,这是迭代器协议的一部分。
# 迭代器协议在 :
# Python 中,要实现一个迭代器,一个类需要实现两个方法: __iter__() 和 __next__() 。
# __iter__() : 返回迭代器对象本身。
# __next__() : 返回容器的下一个元素,并在没有更多元素时抛出 StopIteration 异常。
# 这段代码定义了一个类的 __next__ 方法,它是 Python 中实现迭代器协议的一部分。 __next__ 方法用于返回容器的下一个元素,并在没有更多元素时抛出 StopIteration 异常。
# 方法签名。 self : 类实例的引用。
def __next__(self):
# 返回 源路径 、 转换后的图像 和 原始图像 以供处理。
"""Returns source paths, transformed and original images for processing."""
# 递增 self.count 计数器,用于跟踪迭代进度。
self.count += 1
# 处理图像缓冲区。
# 这段代码是 __next__ 方法中的一部分,它负责从每个视频流的缓冲区中获取图像帧。
# 初始化图像列表。创建一个空列表 images ,用于存储从每个视频流缓冲区中获取的图像。
images = []
# enumerate(iterable, start=0)
# enumerate() 是 Python 内置的一个函数,它用于将一个可迭代对象(如列表、元组、字符串等)组合为一个索引序列,同时列出数据和数据下标。这在 Python 编程中非常有用,尤其是当你需要在循环中同时获取元素和它们的索引时。
# 参数 :
# iterable : 一个可迭代对象,比如列表、元组、字符串等。
# start=0 : 一个可选的整数参数,用于指定索引的起始值,默认为 0。
# 返回值 :
# 返回一个枚举对象,该对象生成包含索引和值的元组。
# enumerate() 函数在循环中提供了一种简洁的方式来跟踪当前元素的索引,这使得代码更加清晰和易于维护。
# 遍历视频流缓冲区。使用 enumerate 函数遍历 self.imgs 列表, i 是索引, x 是当前索引对应的视频流缓冲区(列表或队列)。
for i, x in enumerate(self.imgs):
# Wait until a frame is available in each buffer
# 等待缓冲区中有帧可用。使用 while 循环等待直到缓冲区 x 中有可用的帧。
while not x:
# 检查线程状态和用户输入。
# cv2.waitKey(delay)
# cv2.waitKey() 是 OpenCV 库中的一个函数,它用于等待特定的毫秒数,在这个时间段内,它允许程序处理其他事件,比如用户输入。如果指定的毫秒数内用户按下了任何键,函数会立即返回按键的 ASCII 码;如果超时,则返回 -1。
# 参数 :
# delay :等待的毫秒数。如果设置为 0,则 cv2.waitKey() 会无限期等待用户的按键。
# 返回值 :
# 如果在指定的时间内检测到按键,则返回按键的 ASCII 码。
# 如果超时,则返回 -1。
# 注意事项 :
# cv2.waitKey() 通常用于创建交互式图像查看器,允许用户通过按键来控制程序的行为。
# 如果 delay 参数设置为 0, cv2.waitKey() 会阻塞程序执行,直到用户按下一个键。
# 如果 delay 参数设置为正值, cv2.waitKey() 会等待指定的毫秒数,如果在这段时间内用户没有按键,函数会超时并返回 -1。
# 检查当前视频流的线程 self.threads[i] 是否仍在运行。如果没有在运行,或者用户按下了 'q' 键(通过 cv2.waitKey(1) 检测),则调用 self.close() 方法关闭所有资源,并抛出 StopIteration 异常以结束迭代。
if not self.threads[i].is_alive() or cv2.waitKey(1) == ord("q"): # q to quit
self.close()
raise StopIteration
# 暂停以避免忙等。
# 忙等(Busy Waiting)是一种在计算机编程中常见的同步策略,特别是在多线程或多进程环境中。它涉及到一个进程或线程不断检查某个条件是否成立(例如,等待一个事件的发生或某个数据的可用性),而不是执行其他有用的工作或进入休眠状态。
# 忙等的特点 :
# CPU资源浪费 : 在忙等期间,进程或线程通常会处于一个循环中,不断地检查条件,这会导致CPU资源的浪费,因为处理器在这段时间内没有执行其他有用的任务。
# 响应性 : 尽管忙等会消耗CPU资源,但它可以提供快速的响应性,因为一旦条件满足,进程或线程可以立即响应,不需要等待操作系统的调度。
# 简单性 : 忙等的实现通常很简单,只需要一个循环和条件检查即可。
# 忙等的场景 :
# 轮询 : 硬件设备的状态检查,程序可能需要不断轮询设备状态,直到设备准备好。
# 同步操作 : 在多线程编程中,一个线程可能需要等待另一个线程完成某些操作,通过忙等来检查操作是否完成。
# 忙等的问题 :
# 效率低下 : 忙等会导致程序效率低下,特别是在等待时间较长的情况下。
# 系统负载增加 : 忙等会增加系统的负载,因为它占用了CPU时间,可能导致系统资源紧张。
# 可扩展性差 : 随着系统规模的扩大,忙等的问题会变得更加严重,因为它不是一种可扩展的同步策略。
# 替代方案 :
# 事件驱动编程 : 使用事件驱动模型,当事件发生时,通过回调函数或其他机制来处理,而不是忙等。
# 多路复用 : 使用如 select() , poll() , epoll() 等系统调用,这些调用可以同时监控多个文件描述符,当有事件发生时才进行处理。
# 条件变量 : 在多线程编程中,使用条件变量(如 threading.Condition 在 Python 中),线程可以在条件不满足时释放CPU,等待条件满足后再继续执行。
# 总的来说,忙等是一种简单但通常不推荐的同步策略,因为它会导致资源浪费和效率低下。在现代编程实践中,通常会寻找更高效的同步机制来替代忙等。
# 调用 time.sleep() 函数暂停执行一段时间,以避免忙等(busy waiting)。暂停时间是根据视频流的帧率计算的, 1 / min(self.fps) 确保等待时间不会超过两帧之间的时间间隔。
time.sleep(1 / min(self.fps))
# 重新检查缓冲区。
# 重新检查 self.imgs[i] 缓冲区是否有帧可用。
x = self.imgs[i]
# 如果缓冲区仍然为空,则记录一条警告日志,表明正在等待第 i 个视频流的帧。
if not x:
LOGGER.warning(f"WARNING ⚠️ Waiting for stream {i}") # 警告⚠️正在等待流{i}。
# 这段代码确保了在所有视频流的缓冲区中都有可用帧之前,迭代器会等待。如果线程不再活跃或用户请求退出,迭代器会优雅地关闭所有资源并结束迭代。这种方法可以避免在没有可用帧时过度占用 CPU。
# 这段代码是 __next__ 方法中的一部分,用于从视频流缓冲区中获取图像帧,并根据是否启用缓冲区来决定如何操作缓冲区。
# Get and remove the first frame from imgs buffer
# 缓冲区操作。
# 如果 self.buffer 为 True ,表示启用了缓冲区,那么代码会从缓冲区 x 的开始位置弹出(移除)第一帧图像,并将其添加到 images 列表中。
if self.buffer:
images.append(x.pop(0))
# Get the last frame, and clear the rest from the imgs buffer
# 如果 self.buffer 为 False ,表示没有启用缓冲区,那么代码会执行以下操作。
else:
# 从缓冲区 x 的末尾位置弹出(移除)最后一帧图像,如果缓冲区 x 不为空。如果缓冲区 x 为空,则添加一个零填充的图像,其形状由 self.shape[i] 指定,数据类型为 np.uint8 (这是 OpenCV 中常用的图像数据类型)。
# 将获取的图像或零填充图像添加到 images 列表中。
images.append(x.pop(-1) if x else np.zeros(self.shape[i], dtype=np.uint8))
# 清空缓冲区 x ,以便下一次迭代时缓冲区是空的。
x.clear()
# 缓冲区策略 :
# 这段代码展示了两种不同的缓冲区策略 :
# 启用缓冲区 ( self.buffer = True ) :
# 在这种情况下,缓冲区会保留多帧图像,每次迭代时只从缓冲区的开始位置移除并返回一帧图像。这允许后续处理多帧图像,适用于需要考虑帧间关系的图像处理任务。
# 不启用缓冲区 ( self.buffer = False ) :
# 在这种情况下,每次迭代只处理一帧图像,即缓冲区中的最后帧。处理完毕后,缓冲区被清空。这种策略适用于只需要处理单帧图像的场景,或者当内存使用需要被严格控制时。
# 这段代码通过条件判断来决定如何从缓冲区中获取和处理图像帧,同时根据是否启用缓冲区来决定缓冲区的操作策略。这种设计使得 __next__ 方法能够灵活地适应不同的图像处理需求。
# 返回值。
# 返回一个元组,包含 视频流的源路径 ( self.sources ), 处理后的图像列表 ( images ),以及一个长度为 self.bs (视频流数量)的 空字符串列表 。
return self.sources, images, [""] * self.bs
# 注意事项 :
# 这个方法假设 self.imgs 是一个列表,其中每个元素是一个队列或列表,用于存储来自相应视频流的图像帧。
# self.fps 是一个包含每个视频流帧率的列表, min(self.fps) 用于计算等待时间。
# self.buffer 标志用于控制是否启用缓冲区。
# 这个方法在每次迭代时都会处理所有视频流,并将图像帧返回给迭代器的使用者。
# 如果用户按下 'q' 键或者线程不再活跃,迭代器会优雅地关闭所有资源并结束迭代。
# 这段代码定义了一个类的 __len__ 方法,它用于返回对象的长度。在 Python 中, __len__ 方法是一个特殊方法(也称为魔术方法或内置方法),当使用内置的 len() 函数时,会自动调用这个方法。
# 方法签名。 self : 类实例的引用。
def __len__(self):
# 返回源对象的长度。
"""Return the length of the sources object."""
# 方法的文档字符串说明这个方法返回“sources object”的长度。
# 返回实例变量 self.bs 的值,这个变量代表长度。
return self.bs # 1E12 frames = 32 streams at 30 FPS for 30 years
# 注意事项 :
# __len__ 方法应该返回一个非负整数,表示对象的长度。
# 如果对象没有长度概念,那么不实现 __len__ 方法是一个好的做法,这样 len() 函数会抛出 TypeError 异常。
# 在某些情况下,如果计算长度的代价很高,可以考虑不实现 __len__ 方法,或者提供一个更高效的 __bool__ 或 __len__ 方法的实现。
4.class LoadScreenshots:
# 这段代码定义了一个名为 LoadScreenshots 的类,它用于捕获屏幕截图并将屏幕内容作为 NumPy 数组返回。
class LoadScreenshots:
# YOLOv8 屏幕截图数据加载器。
# 此类管理屏幕截图图像的加载,以便使用 YOLOv8 进行处理。适用于 `yolo predict source=screen`。
# 方法:
# __iter__ :返回迭代器对象。
# __next__ :捕获下一个屏幕截图并返回它。
"""
YOLOv8 screenshot dataloader.
This class manages the loading of screenshot images for processing with YOLOv8.
Suitable for use with `yolo predict source=screen`.
Attributes:
source (str): The source input indicating which screen to capture.
screen (int): The screen number to capture.
left (int): The left coordinate for screen capture area.
top (int): The top coordinate for screen capture area.
width (int): The width of the screen capture area.
height (int): The height of the screen capture area.
mode (str): Set to 'stream' indicating real-time capture.
frame (int): Counter for captured frames.
sct (mss.mss): Screen capture object from `mss` library.
bs (int): Batch size, set to 1.
monitor (dict): Monitor configuration details.
Methods:
__iter__: Returns an iterator object.
__next__: Captures the next screenshot and returns it.
"""
# 这段代码定义了 LoadScreenshots 类的构造函数 __init__ ,它初始化类实例并设置屏幕截图的相关参数。
# 方法签名。 self : 类实例的引用。
# 1.source : 一个字符串参数,包含屏幕截图的配置信息,格式为 [screen_number left top width height] (像素单位)。
def __init__(self, source):
# 来源 = [屏幕编号左上宽度高度](像素)。
"""Source = [screen_number left top width height] (pixels)."""
# 确保 mss 模块已经安装, mss 是一个用于屏幕截图的Python库。
# def check_requirements(requirements=ROOT.parent / "requirements.txt", exclude=(), install=True, cmds=""):
# -> 检查当前环境中是否安装了某些依赖,并在需要时尝试自动安装它们。函数返回值。如果代码执行到这里,表示所有操作成功,返回 True 。
# -> return True
check_requirements("mss")
# 导入 mss 模块。
import mss # noqa
# 使用 split() 方法将 source 字符串分割成多个部分,第一个部分赋值给 source ,剩余的部分存储在列表 params 中。
source, *params = source.split()
# 初始化屏幕编号为 0 (默认全屏),左上角坐标 left 和 top 以及宽高 width 和 height 初始化为 None 。
self.screen, left, top, width, height = 0, None, None, None, None # default to full screen 0
# 根据 params 列表的长度,设置屏幕编号和截图区域的参数。
# 如果 params 长度为 1 ,设置屏幕编号。
if len(params) == 1:
self.screen = int(params[0])
# 如果 params 长度为 4 ,设置左上角坐标和宽高。
elif len(params) == 4:
left, top, width, height = (int(x) for x in params)
# 如果 params 长度为 5 ,同时设置屏幕编号和左上角坐标及宽高。
elif len(params) == 5:
self.screen, left, top, width, height = (int(x) for x in params)
# 设置模式为 "stream" ,初始化帧计数器 frame ,创建 mss.mss 实例 sct ,设置批量大小 bs 和帧率 fps 。
self.mode = "stream"
self.frame = 0
# mss.mss()
# mss.mss() 是 Python mss 库中的一个函数,用于创建一个 MSS(Multiple Screen Shots)对象,该对象可以捕获屏幕截图。
# 函数定义 :
# with mss.mss() as sct: # 在此执行屏幕截图操作
# 参数 :无参数。
# 返回值 :
# 返回一个 mss 对象,该对象提供了屏幕截图的相关方法。
# 注意事项 :
# mss.mss() 函数通常与 with 语句一起使用,以确保资源的正确管理。
# sct.monitors 返回的监视器列表中,每个监视器都是一个字典,包含监视器的详细信息,如宽度、高度、左上角坐标等。
# sct.grab() 方法可以接收一个监视器字典或一个包含截图区域坐标的字典,用于指定截图的区域。
# mss.mss() 函数是 mss 库的核心,用于捕获屏幕截图,并提供了灵活的截图选项。
# MSS(Multiple Screen Shots)对象,即 mss.mss() 实例,提供了以下属性和方法 :
# 属性 :
# monitors :
# 一个包含所有监视器信息的列表。每个监视器都是一个字典,包含该监视器的分辨率、位置等信息。
# 方法 :
# grab(monitor) :
# 根据提供的监视器信息或区域截图。 monitor 参数可以是一个字典,包含截图区域的 top 、 left 、 width 、 height 等信息。
# shot(output=None, mon=-1, callback=None, title=None, include_layered=False, bbox=None) :
# 保存第一个监视器的截图。如果提供 output 参数,截图将被保存到指定的文件路径。
# save(bbox, output) :
# 这是一个迭代器,用于保存截图到指定路径。 bbox 参数定义了截图区域, output 参数指定了文件路径。
# 图像数据 :
# rgb : 包含截图的 RGB (去除透明度通道)图像数据。
# bgra : 包含截图的 BGRA (包含透明度通道)图像数据。
# 其他工具 :
# mss.tools.to_png(rgb, size, output) : 将 RGB 图像数据保存为 PNG 文件。
# MSS 对象提供了一个高效且灵活的方式来捕获屏幕截图,并支持多种操作系统。通过这些属性和方法,可以轻松地实现全屏截图、部分屏幕截图以及将截图保存为文件。
self.sct = mss.mss()
self.bs = 1
self.fps = 30
# Parse monitor shape
# 解析监视器形状。
# 获取监视器配置,并根据提供的参数设置截图区域。
monitor = self.sct.monitors[self.screen]
# 如果 top 或 left 为 None ,则使用监视器的 top 或 left 值;否则,加上提供的偏移量。
self.top = monitor["top"] if top is None else (monitor["top"] + top)
self.left = monitor["left"] if left is None else (monitor["left"] + left)
# 如果 width 或 height 为 None ,则使用监视器的 width 或 height 值。
self.width = width or monitor["width"]
self.height = height or monitor["height"]
# 创建一个字典 monitor 存储截图区域的配置。
self.monitor = {"left": self.left, "top": self.top, "width": self.width, "height": self.height}
# LoadScreenshots 类的构造函数 __init__ 方法初始化类实例,设置屏幕截图的相关参数,并解析监视器形状以确定截图区域。这个类可以用于捕获屏幕截图,并将截图作为 NumPy 数组返回,适用于需要屏幕截图功能的场景。
# 这段代码定义了一个类的 __iter__ 方法,它是 Python 中实现迭代器协议的一部分。 __iter__ 方法允许一个对象成为迭代器,当该对象被用于 for 循环或者需要迭代时,这个方法会被调用。
# 方法签名。 self : 类实例的引用。
def __iter__(self):
# 返回对象的迭代器。
"""Returns an iterator of the object."""
# 返回对象本身,这是实现迭代器协议的关键部分。这意味着对象既是迭代器也是被迭代的对象(即,对象可以自我迭代)。
# 对象可以自我迭代(self-iterating)意味着这个对象既是一个可迭代对象(iterable),又是一个迭代器(iterator)。在 Python 中,这通常通过实现两个双下方法(dunder methods)来实现 : __iter__() 和 __next__() 。
# 一个简单的例子是 Python 中的 dict 类型,它既是可迭代的(可以迭代其键),也是一个迭代器(迭代器可以迭代键、值或键值对)。
# 但是,需要注意的是, dict 本身并不直接实现 __next__() 方法,而是它的视图对象(如 dict.keys() 、 dict.values() 、 dict.items() )实现了 __next__() 方法。
return self
# 实现迭代器时,确保 __next__ 方法在没有更多元素时能够抛出 StopIteration 异常,这是迭代器协议的一部分。
# 如果一个类只实现了 __iter__ 方法而没有实现 __next__ 方法,那么它本身不能被用作迭代器,但可以作为可迭代对象的迭代器。
# 这段代码定义了一个类的 __next__ 方法,它是 Python 中实现迭代器协议的一部分。 __next__ 方法用于返回容器的下一个元素,并在没有更多元素时抛出 StopIteration 异常。在这个特定的例子中, __next__ 方法用于屏幕截图,并将截图数据作为 NumPy 数组返回。
# 方法签名。 self : 类实例的引用。
def __next__(self):
# 使用“mss”进行屏幕捕获,从屏幕获取原始像素作为 np 数组。
"""Screen capture with 'mss' to get raw pixels from the screen as np array."""
# 调用 self.sct.grab(self.monitor) 方法来捕获屏幕的指定区域(由 self.monitor 定义)。
# 使用 np.asarray() 将截图结果转换为 NumPy 数组。
# 通过索引 [:, :, :3] 将像素格式从 BGRA(包含透明度通道)转换为 BGR(去除透明度通道)。
im0 = np.asarray(self.sct.grab(self.monitor))[:, :, :3] # BGRA to BGR
# 构建一个字符串 s ,描述截图的屏幕编号和区域位置及大小。
s = f"screen {self.screen} (LTWH): {self.left},{self.top},{self.width},{self.height}: " # 屏幕 {self.screen} (LTWH): {self.left},{self.top},{self.width},{self.height}:
# 递增 self.frame 计数器,用于跟踪迭代的帧数。
self.frame += 1
# 返回一个包含三个元素的元组。 一个包含屏幕编号的字符串列表。 一个包含截图的 NumPy 数组的列表。 一个包含描述截图区域的字符串列表。
return [str(self.screen)], [im0], [s] # screen, img, string
# __next__ 方法在每次迭代时捕获屏幕的一帧,并将其作为 BGR 格式的 NumPy 数组返回。这个方法使得类实例可以作为一个迭代器,用于实时屏幕捕获或屏幕录制。每次调用 __next__ 方法时,都会返回下一帧的屏幕数据,直到没有更多的帧可以捕获(在这种情况下,应该抛出 StopIteration 异常,但这个方法没有显示抛出该异常,这可能是一个遗漏)。
# LoadScreenshots 类提供了一个简单的方式,通过 mss 模块捕获屏幕截图,并将截图作为 NumPy 数组返回。它支持指定屏幕区域和屏幕编号,使得可以从特定屏幕或屏幕区域捕获图像。这个类可以用于屏幕监控、自动化测试或其他需要屏幕截图的场景。
5.class LoadImagesAndVideos:
# 这段代码定义了一个名为 LoadImagesAndVideos 的类,它用于加载图像和视频文件,并初始化一个数据加载器。
class LoadImagesAndVideos:
# YOLOv8 图像/视频数据加载器。
# 此类管理 YOLOv8 的图像和视频数据的加载和预处理。它支持从各种格式加载,包括单个图像文件、视频文件以及图像和视频路径列表。
# 方法:
# _new_video(path) :为给定的视频路径创建一个新的 cv2.VideoCapture 对象。
"""
YOLOv8 image/video dataloader.
This class manages the loading and pre-processing of image and video data for YOLOv8. It supports loading from
various formats, including single image files, video files, and lists of image and video paths.
Attributes:
files (list): List of image and video file paths.
nf (int): Total number of files (images and videos).
video_flag (list): Flags indicating whether a file is a video (True) or an image (False).
mode (str): Current mode, 'image' or 'video'.
vid_stride (int): Stride for video frame-rate, defaults to 1.
bs (int): Batch size, set to 1 for this class.
cap (cv2.VideoCapture): Video capture object for OpenCV.
frame (int): Frame counter for video.
frames (int): Total number of frames in the video.
count (int): Counter for iteration, initialized at 0 during `__iter__()`.
Methods:
_new_video(path): Create a new cv2.VideoCapture object for a given video path.
"""
# __init__ 方法。
# 1.path : 可以是一个文件路径、一个包含文件路径的列表/元组,或者是一个包含图像/视频/目录路径的 .txt 文件。
# 2.batch : 批量大小,默认为 1。
# 3.vid_stride : 视频帧率步长,默认为 1。
def __init__(self, path, batch=1, vid_stride=1):
# 初始化 Dataloader ,如果找不到文件则引发 FileNotFoundError 。
"""Initialize the Dataloader and raise FileNotFoundError if file not found."""
# 这段代码是 LoadImagesAndVideos 类的构造函数 __init__ 的一部分,它处理输入路径,特别是当路径指向一个 .txt 文件时。
# 初始化一个变量 parent 用于存储 .txt 文件的父目录路径,最初设置为 None 。
parent = None
# 检查 path 参数是否为字符串类型,并且该字符串代表的路径后缀是否为 .txt 。这用于确定 path 是否指向一个文本文件,该文本文件中每行包含一个图像、视频或目录的路径。
if isinstance(path, str) and Path(path).suffix == ".txt": # *.txt file with img/vid/dir on each line
# Path(path).parent
# Path(path).parent 是 Python pathlib 模块中的 Path 类的一个属性,它用于获取路径的父目录。
# 参数 :
# path : 一个字符串,表示文件或目录的路径。
# 返回值 :
# 返回一个 Path 对象,表示给定路径的父目录。
# 注意事项 :
# Path 对象的 parent 属性总是返回父目录的 Path 对象,无论原始路径是文件还是目录。
# 如果原始路径已经是根目录, parent 属性将返回根目录的父目录,即根目录本身(例如,在 Unix 系统中是 / ,在 Windows 系统中是 C:\ )。
# pathlib 模块是 Python 3.4 及以上版本中的标准库,用于处理文件系统路径。
# 如果 path 是一个 .txt 文件的路径,那么获取这个文件的父目录路径并存储在 parent 变量中。 Path(path).parent 是 pathlib 模块中的用法,用于获取路径的父目录。
parent = Path(path).parent
# 读取 .txt 文件的内容,并将其分割成行,每行作为一个源路径,存储在 path 变量中。这样, path 就从一个单一的文件路径变成了一个包含多个源路径的列表。
path = Path(path).read_text().splitlines() # list of sources
# 初始化一个空列表 files ,用于存储找到的所有文件路径,这些文件路径将在后面被分类为图像或视频文件。
files = []
# 这段代码处理了当输入路径为 .txt 文件时的情况,它读取文件中的每行作为源路径,并准备了一个空列表 files 来存储这些路径。接下来的代码将遍历这些路径,将它们分类为图像或视频文件,并进行进一步的处理。这种方法允许用户通过一个 .txt 文件指定多个图像和视频源,使得数据加载更加灵活。
# 这段代码是 LoadImagesAndVideos 类的构造函数 __init__ 的一部分,它负责处理输入的路径,并将符合条件的文件路径添加到 files 列表中。
# 这行代码使用了一个条件表达式来决定如何遍历 path 。如果 path 是一个列表或元组,那么它将遍历 path 中的每个元素。如果 path 不是列表或元组(即是一个单个路径),那么它将遍历包含单个 path 的列表。
for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
# path.absolute()
# 在 Python 的 pathlib 模块中, Path 类的 absolute() 方法用于返回路径的绝对版本。这个方法不会检查文件是否存在,它只是将相对路径转换为绝对路径。
# 参数 :无参数。
# 返回值 :
# 返回一个 Path 对象,表示原始路径的绝对版本。
# 注意事项 :
# 如果原始路径已经是绝对路径, absolute() 方法将返回原始路径的一个副本。
# absolute() 方法不会检查文件或目录是否存在,它只是进行路径的转换。
# 在处理路径时,使用 pathlib 模块可以避免不同操作系统之间的兼容性问题,因为 pathlib 会自动处理不同操作系统的路径分隔符差异。
# absolute() 方法是处理文件路径时常用的方法之一,它确保路径是绝对的,这对于文件操作和路径比较非常有用。
# 对于每个路径 p ,使用 Path(p).absolute() 获取其绝对路径,并转换为字符串。这里没有使用 .resolve() 方法,因为在处理网络共享路径时可能会遇到权限问题,这是根据 Ultralytics 的 GitHub 问题 #2912 中的讨论得出的。
a = str(Path(p).absolute()) # do not use .resolve() https://github.com/ultralytics/ultralytics/issues/2912
# 如果路径 a 中包含通配符 * ,则使用 glob.glob(a, recursive=True) 来匹配所有符合条件的文件路径,并将它们添加到 files 列表中。 recursive=True 参数表示递归搜索子目录。
if "*" in a:
files.extend(sorted(glob.glob(a, recursive=True))) # glob
# 如果路径 a 是一个目录,则使用 glob.glob(os.path.join(a, "*.*")) 来获取该目录下的所有文件路径,并将它们添加到 files 列表中。
elif os.path.isdir(a):
files.extend(sorted(glob.glob(os.path.join(a, "*.*")))) # dir
# 如果路径 a 是一个文件,则直接将其添加到 files 列表中。
elif os.path.isfile(a):
files.append(a) # files (absolute or relative to CWD)
# 如果 path 是相对于 .txt 文件的父目录的相对路径,并且该路径对应的文件存在,则将其绝对路径添加到 files 列表中。
elif parent and (parent / p).is_file():
files.append(str((parent / p).absolute())) # files (relative to *.txt file parent)
# 如果上述条件都不满足,即路径 p 不存在,则抛出 FileNotFoundError 异常。
else:
raise FileNotFoundError(f"{p} does not exist") # {p} 不存在。
# 这段代码处理了多种路径情况,包括通配符路径、目录路径、文件路径以及相对于 .txt 文件父目录的相对路径。它将所有符合条件的文件路径收集到 files 列表中,以便后续处理。如果路径不存在,则抛出异常。这种方法使得 LoadImagesAndVideos 类能够灵活地处理各种输入路径,确保只有有效的文件路径被加载。
# Define files as images or videos
# 这段代码是 LoadImagesAndVideos 类的构造函数 __init__ 的一部分,它负责将加载的文件分类为图像和视频,并初始化类的一些属性。
# 分类文件。初始化两个空列表, images 用于存储图像文件路径, videos 用于存储视频文件路径。
images, videos = [], []
# 遍历 files 列表中的每个文件路径。
for f in files:
# 使用 split() 方法和 [-1] 索引获取文件扩展名,并使用 lower() 方法将其转换为小写。
suffix = f.split(".")[-1].lower() # Get file extension without the dot and lowercase
# 检查文件扩展名是否在 IMG_FORMATS 列表中(这是一个包含所有支持的图像文件扩展名的列表),如果是,则将文件路径添加到 images 列表中。
if suffix in IMG_FORMATS:
images.append(f)
# 如果文件扩展名在 VID_FORMATS 列表中(这是一个包含所有支持的视频文件扩展名的列表),则将文件路径添加到 videos 列表中。
elif suffix in VID_FORMATS:
videos.append(f)
# 初始化类属性。
# 计算 images 和 videos 列表的长度,分别存储在 ni 和 nv 中。
ni, nv = len(images), len(videos)
# 将 images 和 videos 列表合并,并存储在 self.files 中。
self.files = images + videos
# 设置 self.nf 为文件总数, self.ni 为图像文件数。
self.nf = ni + nv # number of files
self.ni = ni # number of images
# 创建一个布尔值列表 self.video_flag ,其中包含 ni 个 False 值和 nv 个 True 值,用于标记文件是图像还是视频。
self.video_flag = [False] * ni + [True] * nv
# 设置类模式为 "image"。
self.mode = "image"
# 设置视频帧率步长为 vid_stride 。
self.vid_stride = vid_stride # video frame-rate stride
# 设置批量大小为 batch 。
self.bs = batch
# 如果 videos 列表不为空,则调用 _new_video 方法处理第一个视频文件。
if any(videos):
self._new_video(videos[0]) # new video
# 如果没有视频文件,则设置 self.cap 为 None 。
else:
self.cap = None
# 如果 self.nf 为 0,即没有找到任何图像或视频文件,则抛出 FileNotFoundError 异常,并提供一条错误消息。
if self.nf == 0:
raise FileNotFoundError(f"No images or videos found in {p}. {FORMATS_HELP_MSG}") # 在 {p} 中未找到任何图像或视频。{FORMATS_HELP_MSG}。
# 这段代码处理文件分类,初始化类属性,并在没有找到任何图像或视频文件时抛出异常。它确保 LoadImagesAndVideos 类能够正确地加载和分类图像和视频文件,为后续的处理提供基础。
# LoadImagesAndVideos 类提供了一个灵活的方式来加载图像和视频文件,支持从单个文件、目录或 .txt 文件中读取路径。它将文件分类为图像和视频,并提供了一些基本属性来管理这些文件。这个类可以用于数据预处理、模型训练或视频流处理等场景。
# 这段代码定义了一个类的 __iter__ 方法,它使得该类实例成为一个迭代器。
# 方法签名。 self : 类实例的引用。
def __iter__(self):
# 返回 VideoStream 或 ImageFolder 的迭代器对象。
"""Returns an iterator object for VideoStream or ImageFolder."""
# 初始化或重置 self.count 计数器为 0。这个计数器通常用于跟踪迭代过程中的当前位置。
self.count = 0
# 返回对象自身,使得对象可以作为迭代器使用。这是实现迭代器协议的一部分,意味着对象既是可迭代的,也是迭代器。
return self
# 这段代码定义了一个类的 __next__ 方法,它用于返回视频流或图像文件夹中的下一批图像或视频帧,以及它们的路径和元数据。
# 方法签名。 self : 类实例的引用。
def __next__(self):
# 返回下一批图像或视频帧及其路径和元数据。
"""Returns the next batch of images or video frames along with their paths and metadata."""
# 初始化三个空列表。 paths 用于存储路径, imgs 用于存储图像或视频帧, info 用于存储元数据。
paths, imgs, info = [], [], []
# 使用 while 循环来收集一批图像或视频帧,直到 imgs 列表的长度等于批量大小 self.bs 。
while len(imgs) < self.bs:
# 检查是否到达文件列表的末尾。
if self.count >= self.nf: # end of file list
# 如果是,且 imgs 列表不为空,则返回当前批次;如果 imgs 列表为空,则抛出 StopIteration 异常,表示迭代结束。
if imgs:
return paths, imgs, info # return last partial batch
else:
raise StopIteration
# 获取当前文件的路径。
path = self.files[self.count]
# 检查当前路径是否对应视频文件。
if self.video_flag[self.count]:
# 设置模式为 "video"。
self.mode = "video"
# 如果没有打开的视频捕获对象或当前视频已结束,则调用 _new_video 方法打开新视频。
if not self.cap or not self.cap.isOpened():
self._new_video(path)
# 这段代码是 __next__ 方法中处理视频帧捕获的一部分。它用于从视频流中按照指定的帧率步长 self.vid_stride 来抓取帧。
# 这行代码创建了一个循环,循环次数由 self.vid_stride 决定,这是一个整数,表示视频帧率的步长。例如,如果 self.vid_stride 是 1,则表示捕获视频的每一帧;如果是 2,则表示每隔一帧捕获一帧。
for _ in range(self.vid_stride):
# success = cap.grab()
# 在 OpenCV 中, grab() 方法是与视频捕获对象( cv2.VideoCapture )一起使用的成员函数。它用于从视频流中捕获(抓取)下一帧。
# 参数 :无参数。
# 返回值 :
# success : 一个布尔值,表示是否成功抓取到帧。
# 注意事项 :
# grab() 方法是非阻塞的,它不会等待帧的到达,如果帧不可用,它会立即返回。
# grab() 方法通常与 retrieve() 方法一起使用, grab() 用于抓取帧,而 retrieve() 用于检索由 grab() 抓取的帧的数据。
# 在调用 grab() 方法后,如果返回 True ,则可以安全地调用 retrieve() 方法来获取帧的图像数据。
# grab() 方法不会改变视频文件的当前位置,因此如果需要连续读取帧,需要在循环中反复调用 grab() 和 retrieve() 方法。
# 在每次循环中,调用 self.cap.grab() 方法来抓取一帧视频数据。 grab() 方法是非阻塞的,它尝试从视频流中捕获一帧,但不等待帧的到达。 success 变量将存储抓取操作的结果,如果成功则为 True ,失败则为 False 。
success = self.cap.grab()
# 如果 grab() 方法没有成功抓取到帧( success 为 False ),则 if 语句会触发,循环将被 break 语句终止。
# 这可能发生在两种情况下 : 视频流已经到达末尾,没有更多的帧可以抓取。 视频流出现了错误,无法继续抓取帧。
if not success:
break # end of video or failure
# 这段代码的目的是按照视频帧率步长从视频流中抓取帧,如果视频流结束或出现错误,则停止抓取。这是处理视频流时常见的模式,特别是在需要对视频帧进行处理或分析的应用中。通过控制 self.vid_stride ,可以有效地减少处理的数据量,例如,对于高帧率视频,可能不需要处理每一帧,而是每隔几帧处理一次。
# 这段代码是 __next__ 方法中处理视频帧捕获和检索的部分。它负责从视频捕获对象 self.cap 中检索帧,并更新相关的路径、图像和信息列表。
# 如果 grab() 方法成功抓取到帧( success 为 True ),则调用 self.cap.retrieve() 方法来检索抓取到的帧。 retrieve() 方法返回一个布尔值 success 和帧数据 im0 。如果检索成功, success 将为 True , im0 将包含帧的图像数据。
if success:
# ret, frame = cap.retrieve()
# 在 OpenCV 中, retrieve() 方法是与视频捕获对象( cv2.VideoCapture )一起使用的成员函数。它用于检索由 grab() 方法捕获的视频帧。
# 参数 :无参数。
# 返回值 :
# ret : 一个布尔值,表示是否成功检索到帧。
# frame : 如果 ret 为 True ,则 frame 包含检索到的帧的图像数据;如果 ret 为 False ,则 frame 可能未定义或为空。
# 注意事项 :
# retrieve() 方法应该在 grab() 方法之后使用,因为 grab() 方法将帧放入缓冲区,而 retrieve() 方法则从缓冲区中检索帧。
# 在使用 retrieve() 方法时,通常需要检查返回的布尔值,以确定是否成功检索到帧。
# retrieve() 方法在读取视频帧时不会改变视频文件的当前位置,因此如果需要连续读取帧,需要在循环中反复调用 grab() 和 retrieve() 方法。
success, im0 = self.cap.retrieve()
# 如果 retrieve() 方法成功检索到帧( success 为 True ),则执行以下操作
if success:
# 递增帧计数器 self.frame 。
self.frame += 1
# 将当前视频文件的路径 path 添加到 paths 列表中。
paths.append(path)
# 将检索到的帧 im0 添加到 imgs 列表中。
imgs.append(im0)
# 将包含视频信息的字符串添加到 info 列表中。
info.append(f"video {self.count + 1}/{self.nf} (frame {self.frame}/{self.frames}) {path}: ") # 视频 {self.count + 1}/{self.nf} (帧 {self.frame}/{self.frames}) {path}:
# 检查是否到达视频末尾( self.frame 等于视频总帧数 self.frames )。
if self.frame == self.frames: # end of video
# 如果是,则递增文件计数器 self.count 并释放当前视频捕获对象 self.cap 。
self.count += 1
self.cap.release()
# 如果 retrieve() 方法没有成功检索到帧( success 为 False ),则执行以下操作。
else:
# Move to the next file if the current video ended or failed to open
# 递增文件计数器 self.count 。
self.count += 1
# 如果当前有打开的视频捕获对象 self.cap ,则释放它。
if self.cap:
self.cap.release()
# 如果还有更多的视频文件要处理( self.count 小于总文件数 self.nf ),则调用 self._new_video() 方法打开下一个视频文件。
if self.count < self.nf:
self._new_video(self.files[self.count])
# 这段代码处理视频帧的检索和视频文件的迭代。它确保了在成功检索到帧后,相关的路径、图像和信息被正确地添加到列表中,并在视频结束或无法检索到帧时,移动到下一个视频文件。这种处理方式保证了视频流的连续性和迭代器的正确行为。
# 这段代码是 __next__ 方法中处理图像文件的部分。它负责从文件系统中读取图像,并更新相关的路径、图像和信息列表。
# 如果当前处理的不是视频文件(即 self.video_flag[self.count] 为 False ),则设置模式为 "image"。
else:
self.mode = "image"
# 使用 OpenCV 的 cv2.imread() 函数尝试读取路径 path 指向的图像文件。这个函数返回一个图像矩阵,如果图像成功加载,则 im0 将包含图像的 BGR 像素数据。
im0 = cv2.imread(path) # BGR
# 如果 cv2.imread() 函数返回 None ,表示图像文件无法加载。这时,使用 LOGGER 记录一条警告信息,指出图像读取错误。
if im0 is None:
LOGGER.warning(f"WARNING ⚠️ Image Read Error {path}") # 警告 ⚠️ 图像读取错误 {path}。
# 如果图像成功加载。
else:
# 将图像的路径 path 添加到 paths 列表中。
paths.append(path)
# 将图像矩阵 im0 添加到 imgs 列表中。
imgs.append(im0)
# 将包含图像信息的字符串添加到 info 列表中。
info.append(f"image {self.count + 1}/{self.nf} {path}: ") # 图像 {self.count + 1}/{self.nf} {path}:
# 递增文件计数器 self.count ,准备处理下一个文件。
self.count += 1 # move to the next file
# 检查是否已经处理完所有图像文件( self.count 大于或等于图像文件总数 self.ni )。如果是,则退出循环。
if self.count >= self.ni: # end of image list
break
# 这段代码处理图像文件的读取和记录。它确保了在成功读取图像后,相关的路径、图像和信息被正确地添加到列表中,并在到达图像列表末尾时退出循环。这种处理方式保证了图像列表的连续性和迭代器的正确行为。如果图像无法加载,它会记录一条警告信息,而不是抛出异常,这样可以继续处理列表中的其他图像文件。
# 返回包含 路径 、 图像或视频帧 和 元数据的列表 。
return paths, imgs, info
# __next__ 方法用于从视频流或图像文件夹中按批次返回图像或视频帧,以及它们的路径和元数据。它处理视频和图像文件的不同情况,并在到达文件列表末尾时抛出 StopIteration 异常。这个方法是实现迭代器协议的关键部分,使得类实例可以用于 for 循环或其他迭代上下文中。
# 这段代码定义了一个名为 _new_video 的方法,它是 LoadImagesAndVideos 类的一个私有方法,用于为给定的视频路径创建一个新的视频捕获对象。
# 方法签名。
# 1.self : 类实例的引用。
# 2.path : 视频文件的路径。
def _new_video(self, path):
# 为给定的路径创建一个新的视频捕获对象。
"""Creates a new video capture object for the given path."""
# 初始化或重置帧计数器 self.frame 为 0。这个计数器用于跟踪当前处理的视频帧。
self.frame = 0
# 使用 OpenCV 的 cv2.VideoCapture 类创建一个新的视频捕获对象,并将其赋值给 self.cap 。这个对象用于从视频文件中捕获帧。
self.cap = cv2.VideoCapture(path)
# 获取视频的帧率(Frames Per Second),并将其转换为整数后存储在 self.fps 中。
self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
# 检查视频捕获对象是否成功打开。如果没有成功打开,抛出 FileNotFoundError 异常,并提供一条错误消息。
if not self.cap.isOpened():
raise FileNotFoundError(f"Failed to open video {path}") # 无法打开视频 {path}。
# 获取视频的总帧数,并根据视频 帧率步长 self.vid_stride 计算 实际需要处理的帧数 。结果转换为整数后存储在 self.frames 中。
self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.vid_stride)
# _new_video 方法负责为指定的视频路径初始化一个新的视频捕获对象,并设置相关的视频属性,如帧率和总帧数。如果视频文件无法打开,它将抛出异常。这个方法是处理视频文件时的重要部分,确保了视频流的正确加载和帧的迭代处理。
# 这段代码定义了一个类的 __len__ 方法,它用于返回对象的长度。在 Python 中, __len__ 方法是一个特殊方法(也称为魔术方法或内置方法),当使用内置的 len() 函数时,会自动调用这个方法。
# 方法签名。 self : 类实例的引用。
def __len__(self):
# 返回对象中的批次数。
"""Returns the number of batches in the object."""
# 计算批次数,方法是将文件总数 self.nf 除以批量大小 self.bs ,然后向上取整(使用 math.ceil() 函数)。
# 返回计算得到的批次数。
return math.ceil(self.nf / self.bs) # number of files
# __len__ 方法提供了一种方便的方式来获取对象中的批次数,这在处理分批数据时非常有用。例如,在数据加载器中,你可能需要知道总共有多少批数据,以便在训练循环中正确地迭代数据。通过实现 __len__ 方法,类实例可以与 len() 函数一起使用,使得代码更加清晰和 Pythonic。
# "Pythonic" 是一个形容词,用来描述那些遵循 Python 编程语言最佳实践、风格指南和设计哲学的代码或编程习惯。"Pythonic" 代码通常被认为是易于理解和符合 Python 社区约定的代码。
# "Pythonic" 代码通常被认为是高质量、高效和符合 Python 设计哲学的代码。编写 Pythonic 代码有助于保持代码的一致性和可维护性,同时也使得其他 Python 开发者更容易理解和协作。
6.class LoadPilAndNumpy:
# 这段代码定义了一个名为 LoadPilAndNumpy 的类,它用于处理 PIL 图像和 NumPy 数组格式的图像数据。
class LoadPilAndNumpy:
# 从 PIL 和 Numpy 数组加载图像以进行批处理。
# 此类旨在管理从 PIL 和 Numpy 格式加载和预处理图像数据。它执行基本验证和格式转换,以确保图像符合下游处理所需的格式。
# 方法:
# _single_check(im) :验证并将单个图像格式化为 Numpy 数组。
"""
Load images from PIL and Numpy arrays for batch processing.
This class is designed to manage loading and pre-processing of image data from both PIL and Numpy formats.
It performs basic validation and format conversion to ensure that the images are in the required format for
downstream processing.
Attributes:
paths (list): List of image paths or autogenerated filenames.
im0 (list): List of images stored as Numpy arrays.
mode (str): Type of data being processed, defaults to 'image'.
bs (int): Batch size, equivalent to the length of `im0`.
Methods:
_single_check(im): Validate and format a single image to a Numpy array.
"""
# __init__ 方法。
#1.im0 : 可以是单个图像或图像列表,图像可以是 PIL Image.Image 对象或 NumPy 数组。
def __init__(self, im0):
# 初始化 PIL 和 Numpy 数据加载器。
"""Initialize PIL and Numpy Dataloader."""
# 如果 im0 不是列表,则将其转换为包含单个元素的列表。
if not isinstance(im0, list):
im0 = [im0]
# getattr(object, name, default=None)
# getattr 是 Python 内置的一个函数,用于获取对象的属性值。这个函数可以用来动态地访问对象的属性,尤其是在属性名称在代码运行时才知道的情况下。
# object :要获取属性值的对象。
# name :要获取的属性的名称,它应该是一个字符串。
# default :可选参数,如果属性 name 在 object 中不存在时返回的默认值,默认为 None 。
# 功能 :
# getattr 函数返回 object 的 name 指定的属性值。如果 name 指定的属性在 object 中不存在,并且提供了 default 参数,则返回 default 指定的值;如果没有提供 default 参数,则抛出 AttributeError 异常。
# 返回值 :
# getattr 函数返回指定属性的值,或者在属性不存在时返回默认值。
# 注意事项 :
# 使用 getattr 时需要注意属性名称的字符串格式,因为属性名称会被直接用作对象的属性键。
# getattr 可以用于任何对象,包括自定义类的实例、内置类型的对象等。
# getattr 是一个非常有用的函数,特别是在处理动态属性名或需要在属性不存在时提供默认值的场景。
# 为每个图像创建一个路径列表 self.paths 。如果图像有 filename 属性,则使用该属性作为路径;否则,使用默认名称 image{i}.jpg ,其中 i 是图像的索引。
self.paths = [getattr(im, "filename", f"image{i}.jpg") for i, im in enumerate(im0)]
# 使用列表推导式和 _single_check 静态方法检查每个图像,确保它们是正确的格式。
self.im0 = [self._single_check(im) for im in im0]
# 设置模式为 "image"。
self.mode = "image"
# 设置批量大小 self.bs 为图像列表的长度。
self.bs = len(self.im0)
# _single_check 静态方法。
# 这是一个静态方法,用于检查单个图像 1.im 。
@staticmethod
def _single_check(im):
# 验证并将图像格式化为 numpy 数组。
"""Validate and format an image to numpy array."""
# 断言图像 im 是 PIL Image.Image 对象或 NumPy 数组。
assert isinstance(im, (Image.Image, np.ndarray)), f"Expected PIL/np.ndarray image type, but got {type(im)}" # 预期 PIL/np.ndarray 图像类型,但得到 {type(im)}。
# 如果图像是 PIL Image.Image 对象,确保其模式为 "RGB",如果不是,则转换为 "RGB"。
if isinstance(im, Image.Image):
if im.mode != "RGB":
im = im.convert("RGB")
# 将 PIL 图像转换为 NumPy 数组,并调整颜色通道顺序从 RGB 到 BGR(因为 OpenCV 使用 BGR)。
im = np.asarray(im)[:, :, ::-1]
# 确保 NumPy 数组是连续的( np.ascontiguousarray )。
im = np.ascontiguousarray(im) # contiguous
# 返回处理后的图像。
return im
# __len__ 方法。返回图像列表 self.im0 的长度。
def __len__(self):
# 返回“im0”属性的长度。
"""Returns the length of the 'im0' attribute."""
# 返回图像数量。
return len(self.im0)
# __next__ 方法。返回下一批图像的路径、图像数据和元数据。
def __next__(self):
# 返回批量路径、图像、已处理的图像、None、''。
"""Returns batch paths, images, processed images, None, ''."""
# 如果 self.count 为 1,表示已经返回过一次数据,因此抛出 StopIteration 异常。
if self.count == 1: # loop only once as it's batch inference
raise StopIteration
# 递增 self.count 。
self.count += 1
# 返回 路径列表 、 图像数据列表 和 元数据列表(这里使用空字符串列表) 。
return self.paths, self.im0, [""] * self.bs
# __iter__ 方法。返回迭代器对象。
def __iter__(self):
# 为 LoadPilAndNumpy 类启用迭代。
"""Enables iteration for class LoadPilAndNumpy."""
# 重置 self.count 为 0。
self.count = 0
# 返回对象自身作为迭代器。
return self
# LoadPilAndNumpy 类用于处理 PIL 图像和 NumPy 数组格式的图像数据,确保它们是正确的格式,并提供迭代器功能。这个类可以用于图像处理和机器学习任务,特别是在需要处理图像批次时。
7.class LoadTensor:
# 这段代码定义了一个名为 LoadTensor 的类,它用于处理图像数据,特别是以张量(Tensor)形式存在的图像数据。
class LoadTensor:
# 从 torch.Tensor 数据加载图像。
# 此类管理从 PyTorch 张量加载和预处理图像数据以供进一步处理。
# 方法:
# _single_check(im, stride) :验证并可能修改输入张量。
"""
Load images from torch.Tensor data.
This class manages the loading and pre-processing of image data from PyTorch tensors for further processing.
Attributes:
im0 (torch.Tensor): The input tensor containing the image(s).
bs (int): Batch size, inferred from the shape of `im0`.
mode (str): Current mode, set to 'image'.
paths (list): List of image paths or filenames.
count (int): Counter for iteration, initialized at 0 during `__iter__()`.
Methods:
_single_check(im, stride): Validate and possibly modify the input tensor.
"""
# __init__ 方法。
# 1.im0 : 传入的图像数据,可以是单个图像或图像列表,图像可以是 PIL Image.Image 对象、NumPy 数组或PyTorch张量。
def __init__(self, im0) -> None:
# 初始化 Tensor Dataloader。
"""Initialize Tensor Dataloader."""
# 调用 _single_check 方法来验证和标准化传入的图像数据 im0 ,确保它们是正确的格式,并将其存储在实例变量 self.im0 中。
self.im0 = self._single_check(im0)
# 获取 self.im0 的形状,并将其第一个维度(通常是批量大小)存储在 self.bs 中。
self.bs = self.im0.shape[0]
# 设置模式为 "image"。
self.mode = "image"
# 为每个图像创建一个路径列表 self.paths 。如果图像有 filename 属性,则使用该属性作为路径;否则,使用默认名称 image{i}.jpg ,其中 i 是图像的索引。
self.paths = [getattr(im, "filename", f"image{i}.jpg") for i, im in enumerate(im0)]
# LoadTensor 类用于处理图像数据,特别是以张量形式存在的图像数据。它初始化图像数据,设置批量大小,并为每个图像创建路径列表。这个类可以用于图像处理和机器学习任务,特别是在需要处理图像批次时。通过实现 _single_check 方法,类确保所有图像数据都是统一的格式,便于后续处理。
# 这段代码定义了一个名为 _single_check 的静态方法,它用于验证和格式化图像数据,确保它们符合 PyTorch 张量的要求。
# 方法签名。
# 1.im : 传入的图像数据,可以是 PyTorch 张量。
# 2.stride : 步长,默认为 32,用于确保图像的高和宽能被步长整除。
@staticmethod
def _single_check(im, stride=32):
# 验证并将图像格式化为 torch.Tensor。
"""Validate and format an image to torch.Tensor."""
# 构建一个警告消息 s ,用于提示输入的张量形状不符合要求。
s = (
f"WARNING ⚠️ torch.Tensor inputs should be BCHW i.e. shape(1, 3, 640, 640) " # 警告 ⚠️ torch.Tensor 输入应为 BCHW,即形状(1、3、640、640)可被步幅 {stride} 整除。输入形状 {tuple(im.shape)} 不兼容。
f"divisible by stride {stride}. Input shape{tuple(im.shape)} is incompatible."
)
# 检查 im 的维度是否为 4(批量大小、通道、高、宽),如果不是,则检查是否为 3(通道、高、宽)。
if len(im.shape) != 4:
if len(im.shape) != 3:
# 如果维度不是 4 也不是 3,则抛出 ValueError 异常。
raise ValueError(s)
# 如果维度为 3,则记录警告,并将维度扩展为 4,以适应批量处理。
LOGGER.warning(s)
im = im.unsqueeze(0)
# 要求图像的宽高能被步长整除通常是因为在深度学习和计算机视觉中,图像数据经常需要被送入神经网络进行处理。神经网络的输入通常有特定的尺寸要求,或者在某些操作中,如卷积,需要输入的尺寸能够被步长(stride)整除以保证输出的尺寸是整数。
# 以下是一些具体的原因 :
# 神经网络输入尺寸 :
# 许多神经网络模型,尤其是那些使用卷积神经网络(CNN)的,要求输入图像的尺寸是特定的。例如,一些流行的预训练模型可能要求输入图像的宽高是 224x224、256x256 或其他尺寸。如果图像的宽高不能被步长整除,可能需要对图像进行裁剪或填充以满足这些尺寸要求。
# 特征图尺寸 :
# 在卷积神经网络中,卷积层的输出(特征图)尺寸会根据输入尺寸、卷积核尺寸、步长和填充来计算。如果输入尺寸不能被步长整除,那么在多次卷积操作后,特征图的尺寸可能会变成非整数,这在大多数神经网络框架中是不允许的。
# 效率和性能 :
# 确保图像的宽高能被步长整除可以提高计算效率。在硬件层面,如 GPU,对齐的内存访问(即数据尺寸是特定步长的倍数)可以减少缓存未命中和提高内存访问速度。
# 池化和下采样 :
# 在池化(pooling)或下采样(downsampling)操作中,如果特征图的尺寸不能被步长整除,那么在这些操作后可能会得到非整数的尺寸,这可能导致信息丢失或需要额外的处理。
# 批处理 :
# 在批处理中,如果每个图像的尺寸不一致,可能会导致内存访问模式变得复杂,影响性能。统一的尺寸可以简化批处理的实现,并提高内存利用率。
# 数据增强 :
# 在数据增强阶段,如随机裁剪或翻转,如果图像尺寸不能被步长整除,可能会在边缘产生不均匀的分布,影响数据增强的效果。
# 因此,为了确保图像数据能够顺利地通过神经网络进行处理,并且在训练和推理时都能保持高效和一致的性能,通常会要求图像的宽高能被步长整除。在实际应用中,这通常通过图像预处理步骤来实现,包括裁剪、填充或调整图像尺寸等操作。
# 检查图像的高和宽是否能被步长 stride 整除。如果不能,则抛出 ValueError 异常。
if im.shape[2] % stride or im.shape[3] % stride:
raise ValueError(s)
# torch.finfo(dtype)
# torch.finfo() 是 PyTorch 库中的一个函数,它用于获取关于浮点数类型的信息。这个函数返回一个 torch._finfo 对象,该对象包含了关于指定浮点类型的几个属性,如最小值、最大值、机器epsilon(机器精度)、数值范围等。
# 参数 :
# dtype : 一个指定的浮点数类型,如 torch.float32 或 torch.float64 。
# 返回值 :
# 返回一个 torch._finfo 对象,该对象包含以下属性 :
# eps : 机器epsilon,即 1.0 与大于 1.0 的最小可表示浮点数之间的差。•
# max : 该类型能表示的最大值。
# min : 该类型能表示的最小值。
# tiny : 该类型能表示的最小非零正数。
# resolution : 两个连续可表示浮点数之间的差。
# 注意事项 :
# 使用 torch.finfo() 可以帮助你了解浮点数类型的限制,特别是在进行数值计算和比较时。
# 在处理浮点数时,了解机器epsilon是非常重要的,因为它决定了两个浮点数被认为是相等的最大差异。
# 检查张量 im 中的最大值是否大于 1.0 加上 im.dtype 的epsilon值(用于处理浮点数精度问题)。
if im.max() > 1.0 + torch.finfo(im.dtype).eps: # torch.float32 eps is 1.2e-07
# 如果是,则记录警告。
LOGGER.warning(
f"WARNING ⚠️ torch.Tensor inputs should be normalized 0.0-1.0 but max value is {im.max()}. " # 警告⚠️torch.Tensor 输入应该标准化为 0.0-1.0,但最大值是 {im.max()}。
f"Dividing input by 255." # 将输入除以 255。
)
# 将张量 im 转换为浮点数类型并除以 255.0,以将其归一化到 0.0-1.0 的范围内。
im = im.float() / 255.0
# 返回处理后的 PyTorch 张量。
return im
# _single_check 方法用于确保传入的图像数据是正确的格式和类型,以便可以在 PyTorch 中使用。它检查维度、步长兼容性和值的范围,并进行必要的转换和归一化。这个方法对于确保图像数据与 PyTorch 模型的输入要求一致非常重要。
# 这段代码定义了一个迭代器类的三个核心方法: __iter__ 、 __next__ 和 __len__ 。这个迭代器类似乎是为了处理一批图像数据而设计的。
# __iter__ 方法。
def __iter__(self):
# 返回一个迭代器对象。
"""Returns an iterator object."""
# 这个方法重置迭代器的状态,将 self.count 设置为 0。
self.count = 0
# 返回迭代器对象本身( self )。这是 Python 迭代器协议的一部分,允许对象在 for 循环中被迭代。
return self
# __next__ 方法。
def __next__(self):
# 返回迭代器中的下一个项目。
"""Return next item in the iterator."""
# 检查 self.count 是否为 1,如果是,则意味着已经到达迭代的末尾,因此抛出 StopIteration 异常来停止迭代。
if self.count == 1:
raise StopIteration
# 如果没有到达末尾, self.count 递增。
self.count += 1
# 返回当前的 路径列表 self.paths 、 图像数据 self.im0 和一个长度为批量大小 self.bs 的 空字符串列表 。
return self.paths, self.im0, [""] * self.bs
# __len__ 方法。
def __len__(self):
# 返回批次大小。
"""Returns the batch size."""
# 这个方法返回迭代器的批量大小,即 self.bs 。这个值表示每次迭代返回的项目数。
return self.bs
# 迭代器类的设计似乎是为了单次迭代一批图像数据。 __iter__ 方法初始化迭代器, __next__ 方法在每次调用时返回下一批数据,直到所有数据被返回,然后抛出 StopIteration 异常。
# __len__ 方法提供了批量大小的信息。
# 这种设计模式适用于需要一次性处理完整数据集的场景,例如在某些数据加载或模型推理任务中。
8.def autocast_list(source):
# 这段代码定义了一个名为 autocast_list 的函数,它用于将不同类型源的数据合并成一个包含 NumPy 数组或 PIL 图像的列表。
# 函数签名。
# 1.source : 输入的源列表,可以包含文件名、URI、PIL 图像或 NumPy 数组。
def autocast_list(source):
# 将不同类型的源列表合并到 numpy 数组或 PIL 图像列表中。
"""Merges a list of source of different types into a list of numpy arrays or PIL images."""
# 初始化一个空列表 files ,用于存储处理后的图像数据。
files = []
# 遍历输入列表 source 中的每个元素 im 。
for im in source:
# 如果 im 是字符串或 Path 对象,检查它是否是一个 HTTP URI 。
if isinstance(im, (str, Path)): # filename or uri
# Image.open(source)
# Image.open() 是 Python Imaging Library (PIL) 中的一个函数,用于打开图像文件并返回一个 PIL Image 对象。这个对象可以用来处理图像数据,例如读取、修改和保存图像。
# 参数 :
# source : 可以是图像文件的路径、一个文件对象,或者是包含图像数据的字节流。
# 返回值 :
# 返回一个 PIL Image 对象,该对象提供了多种方法来操作图像。
# 注意事项 :
# Image.open() 支持多种图像格式,包括 JPEG、PNG、GIF 等。
# 打开图像时,PIL 会自动处理不同格式的图像数据,并将其转换为 PIL Image 对象。
# 如果 source 是一个字节流,需要确保它是可读的,并且包含有效的图像数据。
# PIL Image 对象提供了丰富的方法和属性,可以用于图像处理,如裁剪、旋转、调整大小、颜色转换等。
# Image.open() 是 PIL 库中最基本的函数之一,是处理图像数据的起点。通过这个函数,可以轻松地在 Python 程序中加载和操作图像。
# 如果是 HTTP URI(以 "http" 开头),使用 requests.get() 下载图像数据,并以流模式打开。
# 否则,直接打开本地文件。
# 将打开的 PIL 图像添加到 files 列表中。
files.append(Image.open(requests.get(im, stream=True).raw if str(im).startswith("http") else im))
# 如果 im 是 PIL 图像或 NumPy 数组,直接添加到 files 列表中。
elif isinstance(im, (Image.Image, np.ndarray)): # PIL or np Image
files.append(im)
# 如果 im 不是支持的类型,抛出 TypeError 异常,并提供错误信息和支持的源类型链接。
else:
raise TypeError(
f"type {type(im).__name__} is not a supported Ultralytics prediction source type. \n" # 类型 {type(im).__name__} 不是受支持的 Ultralytics 预测源类型。
f"See https://docs.ultralytics.com/modes/predict for supported source types." # 请参阅https://docs.ultralytics.com/modes/predict 了解支持的源类型。
)
# 返回包含处理后的图像数据的 files 列表。
return files
# autocast_list 函数用于处理多种类型的图像源,包括文件名、URI、PIL 图像和 NumPy 数组,并将它们统一转换为 PIL 图像列表 或 NumPy 数组。这个函数对于需要将不同来源的图像数据统一处理的场景非常有用,例如在图像处理和机器学习任务中。通过这个函数,可以确保输入数据的一致性,简化后续的处理流程。
9.def get_best_youtube_url(url, method="pytube"):
# 这段代码定义了一个名为 get_best_youtube_url 的函数,它用于获取给定 YouTube 视频 URL 的最佳视频流 URL。函数支持三种不同的方法来获取最佳视频流 : pytube 、 pafy 和 yt-dlp 。
# 函数签名。
# 1.url : YouTube 视频的 URL。
# 2.method : 获取最佳视频流的方法,默认为 pytube 。
def get_best_youtube_url(url, method="pytube"):
# 从给定的 YouTube 视频中检索最佳质量 MP4 视频流的 URL。
# 此函数使用指定的方法从 YouTube 中提取视频信息。它支持以下方法:
# - “pytube”:使用 pytube 库获取视频流。
# - “pafy”:使用 pafy 库获取视频流。
# - “yt-dlp”:使用 yt-dlp 库获取视频流。
# 然后,该函数找到具有视频编解码器但没有音频编解码器的最高质量的 MP4 格式,并返回此视频流的 URL。
"""
Retrieves the URL of the best quality MP4 video stream from a given YouTube video.
This function uses the specified method to extract the video info from YouTube. It supports the following methods:
- "pytube": Uses the pytube library to fetch the video streams.
- "pafy": Uses the pafy library to fetch the video streams.
- "yt-dlp": Uses the yt-dlp library to fetch the video streams.
The function then finds the highest quality MP4 format that has a video codec but no audio codec, and returns the
URL of this video stream.
Args:
url (str): The URL of the YouTube video.
method (str): The method to use for extracting video info. Default is "pytube". Other options are "pafy" and
"yt-dlp".
Returns:
(str): The URL of the best quality MP4 video stream, or None if no suitable stream is found.
"""
# 使用 pytubefix 库(而不是 pytube )来解决 pytube 的某些已知问题。
if method == "pytube":
# Switched from pytube to pytubefix to resolve https://github.com/pytube/pytube/issues/1954
# 确保 pytubefix 库已安装,并且版本至少为 6.5.2。
check_requirements("pytubefix>=6.5.2")
# 导入 pytubefix 库中的 YouTube 类。
from pytubefix import YouTube
# 使用 YouTube 类创建一个 YouTube 对象,并获取所有 MP4 格式的视频流。
streams = YouTube(url).streams.filter(file_extension="mp4", only_video=True)
# 按分辨率对视频流进行排序,优先选择分辨率最高的流。
streams = sorted(streams, key=lambda s: s.resolution, reverse=True) # sort streams by resolution
# 遍历排序后的视频流列表,找到第一个分辨率至少为 1080p 的视频流,并返回其 URL。
for stream in streams:
if stream.resolution and int(stream.resolution[:-1]) >= 1080: # check if resolution is at least 1080p
return stream.url
# 使用 pafy 库获取最佳视频流。
elif method == "pafy":
# 确保 pafy 库和特定版本的 youtube_dl 已安装。
check_requirements(("pafy", "youtube_dl==2020.12.2"))
# 导入 pafy 库。
import pafy # noqa
# 使用 pafy.new 创建一个 YouTube 对象。获取最佳 MP4 格式的视频流,并返回其 URL。
return pafy.new(url).getbestvideo(preftype="mp4").url
# 使用 yt-dlp 库获取最佳视频流。
elif method == "yt-dlp":
# 确保 yt-dlp 库已安装。
check_requirements("yt-dlp")
# 导入 yt_dlp 库。
import yt_dlp
# 创建一个 YoutubeDL 对象。
with yt_dlp.YoutubeDL({"quiet": True}) as ydl:
# 提取 YouTube 视频的信息,但不下载视频。
info_dict = ydl.extract_info(url, download=False) # extract info
# 遍历视频格式列表。
for f in reversed(info_dict.get("formats", [])): # reversed because best is usually last
# Find a format with video codec, no audio, *.mp4 extension at least 1920x1080 size
# 优先选择分辨率至少为 1920x1080。
good_size = (f.get("width") or 0) >= 1920 or (f.get("height") or 0) >= 1080
# 视频编码器不为 none 、音频编码器为 none 、文件扩展名为 mp4 的视频流。
if good_size and f["vcodec"] != "none" and f["acodec"] == "none" and f["ext"] == "mp4":
# 返回其 URL
return f.get("url")
# get_best_youtube_url 函数提供了一种灵活的方式来获取 YouTube 视频的最佳视频流 URL,支持三种不同的方法。这个函数可以根据用户的需求和环境选择最合适的方法来获取视频流。
10.# Define constants
# Define constants 定义常量。
# 这段代码定义了一个名为 LOADERS 的元组,它包含了四个不同的类,这些类是用于加载不同类型的图像或视频数据的。每个类都代表了一种特定的数据加载器,它们可以被用来在机器学习模型或计算机视觉任务中加载和预处理数据。
# 以下是对这些类的简要说明 :
# LoadStreams :
# 这个类用于加载视频流数据。它包含了处理实时视频流或视频文件的逻辑,例如从摄像头、网络摄像头或其他视频源获取视频帧。
# LoadPilAndNumpy :
# 这个类用于加载 PIL 图像和 NumPy 数组。PIL(Python Imaging Library)图像是 Python 中处理图像的常用格式,而 NumPy 数组是用于数值计算的多维数组。这个加载器提供了将这些数据类型统一处理的功能。
# LoadImagesAndVideos :
# 这个类用于加载图像和视频文件。它支持从文件系统中读取图像和视频,并包括对它们的预处理,例如调整大小、归一化等。
# LoadScreenshots :
# 这个类用于捕获屏幕截图。它包含了使用 mss 库或其他方法来从屏幕捕获图像的逻辑。
# LOADERS 元组的使用方式可能是在某个数据加载或预处理的上下文中,根据需要加载的数据类型选择合适的加载器。
# 例如,如果需要处理实时视频流,可能会使用 LoadStreams ;如果数据是 PIL 图像和 NumPy 数组,可能会使用 LoadPilAndNumpy 。
# 这样的设计模式提供了灵活性和可扩展性,允许开发者根据具体的应用场景选择或扩展合适的数据加载器。
LOADERS = (LoadStreams, LoadPilAndNumpy, LoadImagesAndVideos, LoadScreenshots)
原文地址:https://blog.csdn.net/m0_58169876/article/details/144331191
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!