YOLOv9-0.1部分代码阅读笔记-plots.py
plots.py
utils\plots.py
目录
plots.py
1.所需的库和模块
2.class Colors:
3.def check_pil_font(font=FONT, size=10):
4.class Annotator:
5.def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')):
6.def hist2d(x, y, n=100):
7.def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
8.def output_to_target(output, max_det=300):
9.def plot_images(images, targets, paths=None, fname='images.jpg', names=None):
10.def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
11.def plot_val_txt():
12.def plot_targets_txt():
13.def plot_val_study(file='', dir='', x=None):
14.def plot_labels(labels, names=(), save_dir=Path('')):
15.def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')):
16.def plot_evolve(evolve_csv='path/to/evolve.csv'):
17.def plot_results(file='path/to/results.csv', dir=''):
18.def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
19.def save_one_box(xyxy, im, file=Path('im.jpg'), gain=1.02, pad=10, square=False, BGR=False, save=True):
1.所需的库和模块
import contextlib
import math
import os
from copy import copy
from pathlib import Path
from urllib.error import URLError
import cv2
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sn
import torch
from PIL import Image, ImageDraw, ImageFont
from utils import TryExcept, threaded
from utils.general import (CONFIG_DIR, FONT, LOGGER, check_font, check_requirements, clip_boxes, increment_path,
is_ascii, xywh2xyxy, xyxy2xywh)
from utils.metrics import fitness
from utils.segment.general import scale_image
# 这段代码包含了一些设置,用于配置环境和图形库的行为。
# Settings
# 从环境变量中获取名为 RANK 的值,并将其转换为整数类型。如果环境变量 RANK 不存在,则默认值为 -1 。 RANK 通常用于分布式训练中标识进程的排名或编号。
RANK = int(os.getenv('RANK', -1))
# 配置了 Matplotlib 的字体设置。 matplotlib.rc() 函数用于设置 Matplotlib 的配置参数。这里,它设置了字体大小为 11。 **{'size': 11} 是一个关键字参数解包,它将字典 {'size': 11} 作为关键字参数传递给 rc 函数。
matplotlib.rc('font', **{'size': 11})
# 设置了 Matplotlib 的后端为 'Agg'。'Agg' 是一个非交互式的后端,用于生成图形并保存到文件中,而不是显示在屏幕上。这通常用于服务器或批处理环境中,其中不需要图形用户界面。
matplotlib.use('Agg') # for writing to files only
# 这段代码的设置主要用于两个目的 :一是配置 Matplotlib 的字体大小,二是指定 Matplotlib 的后端,以便在非交互式环境中生成图形。这些设置对于生成图形并将其保存到文件中非常有用,特别是在自动化脚本或服务器环境中。
2.class Colors:
# 这段代码定义了一个名为 Colors 的类,用于管理和转换颜色值。
# 定义了一个名为 Colors 的新类。
class Colors:
# Ultralytics color palette https://ultralytics.com/
# 这是类的构造函数,用于初始化类的实例。
def __init__(self):
# hex = matplotlib.colors.TABLEAU_COLORS.values()
# 定义了一个元组 hexs ,包含了一系列的十六进制颜色代码。
hexs = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB',
'2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')
# 使用列表推导式和 hex2rgb 方法将 hexs 中的每个十六进制颜色代码转换为 RGB 格式,并存储在 self.palette 中。
self.palette = [self.hex2rgb(f'#{c}') for c in hexs]
# 计算 self.palette 中的颜色数量并存储在 self.n 中。
self.n = len(self.palette)
# 定义了类的 __call__ 方法,允许实例像函数一样被调用。这个方法接受两个参数。
# 1.i :颜色索引。
# 2.bgr :布尔值,指示是否返回BGR格式的颜色。
def __call__(self, i, bgr=False):
# 计算索引 i 对 self.palette 长度的模,以循环访问颜色列表,并获取对应的颜色值。
c = self.palette[int(i) % self.n]
# 如果 bgr 为 True ,则返回BGR格式的颜色(即RGB的逆序);否则,返回RGB格式的颜色。
return (c[2], c[1], c[0]) if bgr else c
# 定义了一个静态方法,它不属于类的任何实例,而属于类本身。
@staticmethod
# 定义了一个静态方法 hex2rgb ,接受一个十六进制颜色代码作为参数。
def hex2rgb(h): # rgb order (PIL)
# 将十六进制颜色代码转换为RGB格式的元组。它通过切片和转换为16进制整数来实现。
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
# Colors 类提供了一个方便的方式来管理和使用一组预定义的颜色。它允许用户通过索引访问颜色,并可以选择以RGB或BGR格式返回颜色值。这个类特别适用于需要循环使用一组固定颜色的场景,例如在绘图或可视化中。通过将十六进制颜色代码转换为RGB格式, Colors 类使得颜色的使用更加灵活和方便。
# 这行代码创建了 Colors 类的一个实例,并将其赋值给变量 colors 。这样做的目的是为了能够在其他地方(例如其他模块或脚本中)方便地导入和使用这个实例,从而访问和操作颜色相关的功能。
colors = Colors() # create instance for 'from utils.plots import colors' 为“从 utils.plots 导入颜色”创建实例。
3.def check_pil_font(font=FONT, size=10):
# 这段代码定义了一个名为 check_pil_font 的函数,其目的是检查并返回一个 PIL(Python Imaging Library,现在称为 Pillow)的 TrueType 字体对象。如果指定的字体文件不存在,它将尝试从网络上下载字体,并将其保存到配置目录( CONFIG_DIR )中。
# 定义了一个名为 check_pil_font 的函数,它接受两个参数。
# 1.font :字体文件的路径,默认为 FONT (这里 FONT 是一个在函数外部定义的变量,指向一个字体文件的路径)。
# 2.size :字体的大小,默认为 10 。
def check_pil_font(font=FONT, size=10):
# Return a PIL TrueType Font, downloading to CONFIG_DIR if necessary
# 将 font 参数转换为 Path 对象,以便使用路径操作。
font = Path(font)
# 检查 font 路径是否存在,如果不存在,则使用配置目录( CONFIG_DIR )和字体文件名构建新的路径。
font = font if font.exists() else (CONFIG_DIR / font.name)
# 尝试使用 ImageFont.truetype 方法加载字体文件。
try:
# ImageFont.truetype(font=None, size=10)
# ImageFont.truetype 是 Python Imaging Library (PIL) 中 ImageFont 模块的一个函数,它用于加载 TrueType 或 OpenType 字体文件,以创建一个字体对象,该对象可以用于在图像上绘制文本。
# 参数说明 :
# font :(可选)一个字符串,指定字体文件的路径。如果未提供或设置为 None ,则会使用 PIL 的默认字体。
# size :(可选)一个整数或浮点数,指定字体的大小,以像素为单位。默认值为 10。
# 返回值 :
# 返回一个 ImageFont 对象,该对象可以用于 ImageDraw 模块中的文本绘制函数。
# 请注意, ImageFont.truetype 函数需要一个有效的 TrueType 或 OpenType 字体文件路径。如果你的系统中没有 Arial 字体文件,你需要指定一个有效的路径到一个存在的字体文件,或者使用系统中可用的其他字体文件。
# 如果 font 参数未提供或为 None ,PIL 将使用一个简单的位图字体,这种字体通常不支持 Unicode 字符,且外观较为简陋。
# 如果 font 存在,就将 font 转换为字符串路径;如果不存在,就使用 font.name 。 size 参数用于指定字体大小。如果成功加载,函数返回这个字体对象。
return ImageFont.truetype(str(font) if font.exists() else font.name, size)
# 如果 ImageFont.truetype 抛出异常(例如,字体文件不存在或损坏),代码将进入这个 except 块。
except Exception: # download if missing
# 尝试调用 check_font 函数检查字体的有效性或者触发字体的下载。
try:
# def check_font(font=FONT, progress=False): -> 检查指定的字体文件是否存在于本地,如果不存在,则从网络上下载该字体文件。
check_font(font)
# 如果 check_font 函数成功执行,代码将再次尝试加载字体,并返回字体对象。
return ImageFont.truetype(str(font), size)
# 如果在加载字体的过程中抛出 TypeError 异常,这可能意味着 Pillow 库的版本不支持某些操作。
except TypeError:
# check_requirements 函数用于检查或提示用户更新 Pillow 库到指定版本。
# def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True, cmds=''): -> 用于检查是否安装了满足YOLO要求的依赖项。如果某些依赖项未安装或版本不兼容,函数会尝试自动安装它们。
check_requirements('Pillow>=8.4.0') # known issue https://github.com/ultralytics/yolov5/issues/5374
# 如果抛出 URLError 异常,这通常意味着代码尝试从网络下载字体时失败了,可能是因为没有网络连接。在这种情况下,函数将返回默认的字体对象。
except URLError: # not online
# PIL.ImageFont.load_default(_size: float | None = None) → FreeTypeFont | ImageFont
# ImageFont.load_default() 是 Python Imaging Library (PIL) 的一个函数,用于加载默认的位图字体。
# 参数 :
# _size (float 或 None): 字体的大小。如果为 None,则使用默认大小。
# 返回值 :
# 返回一个字体对象,可以是 FreeTypeFont 或 ImageFont 类型,具体取决于使用的字体类型。
# 功能描述 :
# ImageFont.load_default() 函数加载 PIL 的默认位图字体,并返回一个字体对象。这个字体对象可以用于 ImageDraw 模块中进行文本绘制。
return ImageFont.load_default()
# 这个函数的目的是确保有一个有效的字体对象可用,如果指定的字体不存在,它会尝试下载或使用默认字体。
4.class Annotator:
# 这段代码定义了一个名为 Annotator 的类,它用于在图像上进行标注,常用于目标检测任务中绘制边界框和标签。
# 定义了一个名为 Annotator 的类。
class Annotator:
# YOLOv5 Annotator for train/val mosaics and jpgs and detect/hub inference annotations YOLOv5 注释器,用于训练/验证马赛克和 jpg 以及检测/集线推理注释。
# 类的构造函数,用于初始化 Annotator 类的实例。它接受以下参数。
# 1.im :要标注的图像,可以是 NumPy 数组或 PIL 图像。
# 2.line_width :标注的线宽,默认为 None,将自动计算。
# 3.font_size :字体大小,默认为 None,将自动计算。
# 4.font :字体文件路径,默认为 'Arial.ttf'。
# 5.pil :一个布尔值,指示是否使用 PIL 库进行标注,默认为 False。
# 6.example :一个示例字符串,用于检查是否包含非ASCII字符(即非拉丁字母)。
def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'):
# 确保在 Annotator 类中进行图像处理之前,图像数据在内存中是连续的,这样可以避免潜在的性能问题,并确保与图像处理库的兼容性。如果图像数据不是连续的,它建议用户在将图像传入 Annotator 之前,先使用 np.ascontiguousarray 函数来转换图像数据。
assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images.' # 图像不连续。将 np.ascontiguousarray(im) 应用于 Annotator() 输入图像。
# 检查示例字符串 example 是否包含非ASCII字符。
# def is_ascii(s=''):
# -> 用于检查一个字符串是否完全由 ASCII 字符组成,即不包含任何非ASCII(如 Unicode 或 UTF-8)字符。比较解码后的字符串长度与原始字符串长度。如果相等,说明原始字符串中不包含任何非ASCII字符,因此它完全由 ASCII 字符组成。
# -> return len(s.encode().decode('ascii', 'ignore')) == len(s)
non_ascii = not is_ascii(example) # non-latin labels, i.e. asian, arabic, cyrillic
# 根据是否指定使用 PIL 或示例字符串中是否包含非ASCII字符来决定是否使用 PIL 库。
self.pil = pil or non_ascii
# 如果决定使用 PIL 库,则执行以下操作。
if self.pil: # use PIL
# Image.fromarray(arr, mode=None)
# Image.fromarray 是 Python Imaging Library (PIL) 中的一个函数,它用于将一个数组(通常是 NumPy 数组)转换成 PIL 图像对象。这个函数非常有用,因为它允许你在图像处理和分析中轻松地在数组和图像对象之间转换。
# 参数说明 :
# arr :一个数组对象,通常是 NumPy 数组。这个数组包含了图像的像素数据。
# mode :(可选)一个字符串,指定图像模式。如果未指定, fromarray 将根据数组的形状和数据类型自动选择一个模式。常见的模式包括 "L"(灰度图),"RGB"(真彩色图像),"RGBA"(带有透明度通道的真彩色图像)等。
# 返回值 :
# 返回一个 PIL 图像对象,你可以使用 PIL 提供的方法对这个对象进行操作,比如旋转、缩放、裁剪等。
# 如果 im 已经是 PIL 图像,则直接使用;否则,将 NumPy 数组转换为 PIL 图像。
self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
# PIL.ImageDraw.Draw(im, mode=None)
# ImageDraw.Draw() 是 Python Imaging Library (PIL) 的一个函数,它用于在给定的图像上进行绘制操作。
# 参数 :
# im :要在其上绘制的图像对象。
# mode :(可选)用于颜色值的模式。对于 RGB 图像,这个参数可以是 RGB 或 RGBA(将绘制内容混合到图像中)。对于所有其他模式,这个参数必须与图像模式相同。如果省略,模式默认为图像的模式。
# 说明 :
# ImageDraw.Draw() 创建一个可以在给定图像上进行绘制的对象。注意,图像将被就地修改。
# ImageDraw 对象提供了一系列的属性和方法,用于在图像上进行绘制操作。以下是一些常用的属性和方法 :
# 属性 :
# im :返回与 ImageDraw 对象关联的图像对象。
# 方法 :
# arc(bounding_box, start, end, fill=None, width=1) 在图像上绘制一个弧线。
# bounding_box :弧线的边界框,是一个四元组 (x0, y0, x1, y1) 。 start :弧线的起始角度(以度为单位)。 end :弧线的结束角度(以度为单位)。 fill :弧线的颜色。 width :弧线的宽度。
# bitmap(xy, bitmap, mask=None) 在图像上绘制一个位图。
# xy :位图左上角的坐标。 bitmap :位图对象。 mask :可选的颜色掩码。
# chord(bounding_box, start, end, fill=None, width=1) 类似于 arc ,但是绘制一个封闭的弦。
# bounding_box :弦的边界框。 start :弦的起始角度。 end :弦的结束角度。 fill :弦的颜色。 width :弦的宽度。
# ellipse(bounding_box, fill=None, outline=None, width=1) 在图像上绘制一个椭圆。
# bounding_box :椭圆的边界框。 fill :椭圆填充的颜色。 outline :椭圆轮廓的颜色。 width :椭圆轮廓的宽度。
# line(xy, fill=None, width=0, joint=None) 在图像上绘制一条线。
# xy :线的起点和终点坐标,是一个序列。 fill :线的颜色。 width :线的宽度。 joint :线段连接处的样式。
# pieslice(bounding_box, start, end, fill=None, outline=None, width=1) 类似于 arc ,但是绘制一个封闭的扇形。
# bounding_box :扇形的边界框。 start :扇形的起始角度。 end :扇形的结束角度。 fill :扇形填充的颜色。 outline :扇形轮廓的颜色。 width :扇形轮廓的宽度。
# point(xy, fill=None) 在图像上绘制点。
# xy :点的坐标。 fill :点的颜色。
# polygon(xy, fill=None, outline=None) 在图像上绘制一个多边形。
# xy :多边形顶点的坐标序列。 fill :多边形填充的颜色。 outline :多边形轮廓的颜色。
# rectangle(xy, fill=None, outline=None, width=1) 在图像上绘制一个矩形。
# xy :矩形左上角和右下角的坐标。 fill :矩形填充的颜色。 outline :矩形轮廓的颜色。 width :矩形轮廓的宽度。
# text(xy, text, fill=None, font=None, anchor=None, *args, **kwargs) 在图像上绘制文本。
# xy :文本左下角的坐标。 text :要绘制的文本。 fill :文本的颜色。 font :字体对象。 anchor :文本的锚点。
# textsize(text, font=None) 返回给定文本和字体的尺寸。
# text :要测量的文本。 font :字体对象。
# 这些方法提供了在图像上进行各种绘制操作的能力,包括线条、形状、文本等。通过这些方法,可以在图像上实现丰富的视觉效果。
# 创建一个用于绘制的 ImageDraw.Draw 对象。
self.draw = ImageDraw.Draw(self.im)
# 选择字体和字体大小。如果 non_ascii 为 True,则使用 'Arial.Unicode.ttf' 字体,否则使用指定的字体。字体大小根据图像尺寸自动计算或使用指定的 font_size 。
# def check_pil_font(font=FONT, size=10):
# -> 检查并返回一个 PIL(Python Imaging Library,现在称为 Pillow)的 TrueType 字体对象。如果指定的字体文件不存在,它将尝试从网络上下载字体,并将其保存到配置目录( CONFIG_DIR )中。
# -> return ImageFont.truetype(str(font) if font.exists() else font.name, size) / return ImageFont.truetype(str(font), size) / return ImageFont.load_default()
self.font = check_pil_font(font='Arial.Unicode.ttf' if non_ascii else font,
size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12))
# 如果不使用 PIL 库,则直接使用原始图像。
else: # use cv2
# 保存图像数据。
self.im = im
# 计算线宽。如果未指定 line_width ,则根据图像尺寸自动计算。
self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width
# 这个 Annotator 类提供了一个灵活的方式来在图像上绘制边界框和标签,支持使用 PIL 或 OpenCV。它考虑了不同语言字符的绘制需求,并提供了自动计算线宽和字体大小的功能。
# 这段代码是 Annotator 类的一个成员函数 box_label ,它用于在图像上绘制边界框并添加文本标签。这个函数根据类是否使用 PIL 或 OpenCV 来绘制不同的图形。
# 定义 box_label 方法,它接受以下参数。
# 1.box :边界框的坐标,格式为 (x1, y1, x2, y2) 。
# 2.label :边界框的文本标签,默认为空字符串。
# 3.color :边界框的颜色,默认为 (128, 128, 128) (灰色)。
# 4.txt_color :文本的颜色,默认为 (255, 255, 255) (白色)。
def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
# Add one xyxy box to image with label
# 检查是否使用 PIL 库或者标签是否包含非ASCII字符。
# def is_ascii(s=''):
# -> 用于检查一个字符串是否完全由 ASCII 字符组成,即不包含任何非ASCII(如 Unicode 或 UTF-8)字符。比较解码后的字符串长度与原始字符串长度。如果相等,说明原始字符串中不包含任何非ASCII字符,因此它完全由 ASCII 字符组成。
# -> return len(s.encode().decode('ascii', 'ignore')) == len(s)
if self.pil or not is_ascii(label):
# 如果使用 PIL,则使用 self.draw.rectangle 方法绘制边界框。
self.draw.rectangle(box, width=self.lw, outline=color) # box
# 如果标签不为空,则继续绘制文本。
if label:
# 获取文本的宽度和高度。
w, h = self.font.getsize(label) # text width, height
# 判断文本是否适合放在边界框的上方。
outside = box[1] - h >= 0 # label fits outside box
# 绘制一个矩形作为文本背景,如果文本适合放在边界框外面,则在边界框上方绘制;否则,在边界框下方绘制。
self.draw.rectangle(
(box[0], box[1] - h if outside else box[1], box[0] + w + 1,
box[1] + 1 if outside else box[1] + h + 1),
fill=color,
)
# self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
# 在矩形内绘制文本。
self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
# 如果不使用 PIL,则使用 OpenCV 的函数绘制边界框和文本。
else: # cv2
# 将边界框坐标转换为整数,并定义两个点 p1 和 p2 。
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
# 使用 OpenCV 的 cv2.rectangle 方法绘制边界框。
cv2.rectangle(self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA)
# 如果标签不为空,则继续绘制文本。
if label:
# 定义字体的厚度。
tf = max(self.lw - 1, 1) # font thickness
# 获取文本的宽度和高度。
w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0] # text width, height
# 判断文本是否适合放在边界框的上方。
outside = p1[1] - h >= 3
p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
# 绘制一个填充的矩形作为文本背景。
cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled
# 使用 OpenCV 的 cv2.putText 方法在矩形内绘制文本。
cv2.putText(self.im,
label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
0,
self.lw / 3,
txt_color,
thickness=tf,
lineType=cv2.LINE_AA)
# 这个 box_label 方法提供了一个灵活的方式来在图像上绘制边界框和文本标签,支持使用 PIL 或 OpenCV。它考虑了不同语言字符的绘制需求,并提供了自动计算文本位置的功能。
# 这段代码是 Annotator 类的一个成员函数 masks ,它用于在图像上叠加多个掩码(masks),并为每个掩码应用指定的颜色。这个函数支持两种情况:当图像在 CPU 上时使用 PIL 或 OpenCV 进行操作,以及当图像在 GPU 上时使用 PyTorch 进行操作。
# 定义 masks 方法,它接受以下参数。
# 1.masks :一个形状为 (h, w, n) 的掩码数组,其中 n 是掩码的数量。
# 2.colors :一个颜色列表,包含每个掩码的颜色,形状为 (r, g, b) 。
# 3.im_gpu :一个可选的 PyTorch 张量,表示在 GPU 上的图像。
# 4.alpha :混合掩码和图像时的透明度,默认为 0.5。
def masks(self, masks, colors, im_gpu=None, alpha=0.5):
# 立即绘制掩膜。
"""Plot masks at once.
Args:
masks (tensor): predicted masks on cuda, shape: [n, h, w]
colors (List[List[Int]]): colors for predicted masks, [[r, g, b] * n]
im_gpu (tensor): img is in cuda, shape: [3, h, w], range: [0, 1]
alpha (float): mask transparency: 0.0 fully transparent, 1.0 opaque
"""
# 如果使用 PIL,则将图像从 PIL 转换为 NumPy 数组。
if self.pil:
# convert to numpy first
self.im = np.asarray(self.im).copy()
# 这段代码是 Annotator 类的 masks 方法的一部分,它处理当 im_gpu 参数为 None 时的情况,即图像不在 GPU 上,而是在 CPU 上。这里的代码负责将多个掩码(masks)应用到图像上,并为每个掩码应用指定的颜色。
# 检查是否提供了在 GPU 上的图像张量 im_gpu 。如果没有提供,说明图像在 CPU 上。
if im_gpu is None:
# Add multiple masks of shape(h,w,n) with colors list([r,g,b], [r,g,b], ...)
# 如果掩码列表为空,即没有掩码需要应用,那么直接返回。
if len(masks) == 0:
return
# 检查掩码是否为 PyTorch 张量。
if isinstance(masks, torch.Tensor):
# 将掩码转换为 PyTorch 张量,并确保数据类型为 uint8 。
masks = torch.as_tensor(masks, dtype=torch.uint8)
# 调整张量维度的顺序,使其从 (h, w, n) 变为 (w, h, n) ,其中 h 是高度, w 是宽度, n 是掩码数量。
masks = masks.permute(1, 2, 0).contiguous()
# 将张量从 GPU 转移到 CPU(如果它在 GPU 上),然后转换为 NumPy 数组。
masks = masks.cpu().numpy()
# masks = np.ascontiguousarray(masks.transpose(1, 2, 0))
# 调整掩码的大小以匹配目标图像的大小。
# def scale_image(im1_shape, masks, im0_shape, ratio_pad=None): -> 将图像的坐标和掩码(masks)从一个尺寸( im1_shape )调整到另一个尺寸( im0_shape )。返回缩放后的 masks 。 -> return masks
masks = scale_image(masks.shape[:2], masks, self.im.shape)
# 确保掩码是 float32 类型的 NumPy 数组。
masks = np.asarray(masks, dtype=np.float32)
# 将颜色列表转换为 float32 类型的 NumPy 数组。
colors = np.asarray(colors, dtype=np.float32) # shape(n,3)
# 计算所有掩码的总和并确保值在 0 和 1 之间是为了处理掩码的重叠部分。在图像处理中,掩码(mask)通常是一个与图像大小相同的二维数组,其中的值表示对应像素是否属于某个特定的区域或对象。当有多个掩码时,它们可能会在某些区域重叠。
# 如果直接将所有掩码相加,重叠区域的值可能会超过 1,这会导致在后续的图像混合过程中出现不正确的颜色值。通过将总和限制在 0 和 1 之间,可以确保每个像素的掩码值在有效范围内,这样在将掩码与图像混合时,可以正确地应用透明度和颜色。
# 具体来说, s = masks.sum(2, keepdims=True).clip(0, 1) 这行代码中, masks.sum(2, keepdims=True) 计算了所有掩码在第三个维度(即掩码数量维度)上的总和, keepdims=True 保持了结果的维度,以便于后续的计算。
# .clip(0, 1) 确保了总和的值在 0 和 1 之间,这样在后续的混合过程中,可以正确地处理重叠区域。
# 这种处理方式在目标检测和分割任务中非常常见,因为它允许在图像上叠加多个掩码,同时保持颜色和透明度的正确性。
# 计算所有掩码的总和,并确保值在 0 和 1 之间。
s = masks.sum(2, keepdims=True).clip(0, 1) # add all masks together
# 将掩码与颜色相乘,得到每个像素的颜色值,并确保值在 0 和 255 之间。
masks = (masks @ colors).clip(0, 255) # (h,w,n) @ (n,3) = (h,w,3)
# 将掩码和图像混合,应用透明度 alpha 。这里 s 是掩码的总和, 1 - s * alpha 是图像的剩余部分,根据掩码的总和调整透明度。
self.im[:] = masks * alpha + self.im * (1 - s * alpha)
# 这段代码处理了在 CPU 上的图像和掩码,将掩码应用到图像上,并根据掩码的总和调整图像的透明度。这样,就可以在图像上叠加半透明的掩码,用于可视化或进一步的处理。
# 这段代码处理当 im_gpu 不为 None 时的情况,即图像在 GPU 上。这里的代码负责将多个掩码(masks)应用到 GPU 上的图像,并为每个掩码应用指定的颜色。
# 这个 else 与之前的 if im_gpu is None: 相对应,表示如果提供了 im_gpu ,则执行这里的代码。
else:
# 如果没有掩码,则直接将 im_gpu 的内容复制到 self.im 。
if len(masks) == 0:
# 将 im_gpu 的通道从 (C, H, W) 格式转换为 (H, W, C) ,然后复制到 CPU 并转换为 NumPy 数组,最后乘以 255 将像素值从 [0, 1] 范围转换为 [0, 255] 。
self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255
# torch.tensor(data, dtype=None, device=None, requires_grad=False)
# torch.tensor() 是 PyTorch 中的一个函数,用于创建一个新的张量(Tensor)。这个函数接受一个数据集合(如列表、元组或NumPy数组)作为输入,并返回一个与之对应的PyTorch张量。
# 参数说明 :
# data : 输入数据,可以是列表、元组、NumPy数组等。
# dtype : 张量的数据类型。如果未指定,PyTorch将根据输入数据自动推断数据类型。
# device : 张量所在的设备。可以是CPU或GPU。如果未指定,默认使用CPU。
# requires_grad : 一个布尔值,指示是否需要计算梯度。默认为 False ,即不需要计算梯度。
# 返回值 :
# 返回一个PyTorch张量,其数据类型和设备由输入参数决定。
# 将颜色列表转换为 PyTorch 张量,并确保值在 [0, 1] 范围内。
colors = torch.tensor(colors, device=im_gpu.device, dtype=torch.float32) / 255.0
# 调整颜色张量的形状,增加两个维度,使其可以与掩码相乘。
colors = colors[:, None, None] # shape(n,1,1,3)
# 调整掩码的形状,增加一个维度,使其可以与颜色相乘。
masks = masks.unsqueeze(3) # shape(n,h,w,1)
# 将掩码与颜色和透明度相乘,得到每个像素的颜色值。
masks_color = masks * (colors * alpha) # shape(n,h,w,3)
# torch.cumprod(input, dim, *, out=None) -> Tensor
# torch.cumprod() 是 PyTorch 库中的一个函数,用于计算张量(tensor)的累积乘积(cumulative product)。这个函数会返回一个新的张量,其中每个元素是输入张量中到当前位置为止的所有元素的乘积。
# 参数 :
# input :输入的张量。
# dim :沿着哪个维度计算累积乘积。
# out :(可选)输出张量,用于存储结果。
# 返回值 :
# 返回一个新的张量,包含了输入张量沿着指定维度的累积乘积。
# torch.cumprod() 函数在处理序列数据或者需要累积计算的场景中非常有用,例如在某些类型的递归神经网络(RNN)或者特定的统计计算中。
# 计算每个像素的累积乘积,用于处理多个掩码的透明度。
inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
# 计算所有掩码颜色的总和。
mcs = (masks_color * inv_alph_masks).sum(0) * 2 # mask color summand shape(n,h,w,3)
# 将 im_gpu 的通道从 [0, 1, 2] 范围翻转为 [1, 2, 0] 。
im_gpu = im_gpu.flip(dims=[0]) # flip channel
# 将 im_gpu 的通道从 (C, H, W) 格式转换为 (H, W, C) 。
im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3)
# 将最后一个掩码的累积乘积应用于 im_gpu ,并加上掩码颜色的总和。
im_gpu = im_gpu * inv_alph_masks[-1] + mcs
# 将 im_gpu 的像素值从 [0, 1] 范围转换为 [0, 255] ,然后转换为 byte 类型,并复制到 CPU 和 NumPy 数组。
im_mask = (im_gpu * 255).byte().cpu().numpy()
# 调整 im_mask 的大小以匹配 self.im 的大小,并将结果复制到 self.im 。
# def scale_image(im1_shape, masks, im0_shape, ratio_pad=None): -> 将图像的坐标和掩码(masks)从一个尺寸( im1_shape )调整到另一个尺寸( im0_shape )。返回缩放后的 masks 。 -> return masks
self.im[:] = scale_image(im_gpu.shape, im_mask, self.im.shape)
# 这段代码处理了在 GPU 上的图像和掩码,将掩码应用到图像上,并根据掩码的总和调整图像的透明度。这样,就可以在图像上叠加半透明的掩码,用于可视化或进一步的处理。
# 如果使用 PIL,则将图像从 NumPy 数组转换回 PIL 图像,并更新绘制对象。
if self.pil:
# convert im back to PIL and update draw
self.fromarray(self.im)
# 这个 masks 方法提供了一个灵活的方式来在图像上叠加掩码,并为每个掩码应用颜色和透明度。它支持在 CPU 和 GPU 上的操作,使得可以在不同的环境下进行图像标注。
# 这段代码定义了 Annotator 类的一个成员函数 rectangle ,它用于在图像上绘制矩形。这个函数特别适用于 PIL(Python Imaging Library)环境,因为使用了 PIL 的 ImageDraw 模块来绘制图形。
# 定义 rectangle 方法,它接受以下参数。
# 1.xy :一个四元组 (x1, y1, x2, y2) ,表示矩形左上角和右下角的坐标。
# 2.fill :填充颜色,默认为 None ,表示不填充。
# 3.outline :矩形边框的颜色,默认为 None ,表示没有边框。
# 4.width :矩形边框的宽度,默认为 1 。
def rectangle(self, xy, fill=None, outline=None, width=1):
# Add rectangle to image (PIL-only) 将矩形添加到图像(仅限 PIL)。
# 调用 PIL 的 ImageDraw 对象的 rectangle 方法来绘制矩形。这个方法接受以下参数。
# xy :矩形的坐标。 fill :填充颜色,如果为 None ,则矩形不被填充。 outline :边框颜色,如果为 None ,则矩形没有边框。 width :边框的宽度。
self.draw.rectangle(xy, fill, outline, width)
# 这个 rectangle 方法提供了一个简单的方式来在图像上绘制矩形,可以用于高亮显示图像中的某个区域或者进行其他类型的标注。由于这个方法依赖于 PIL,因此只能在 PIL 环境下使用,而不能在 OpenCV 环境下使用。
# 这段代码定义了 Annotator 类的一个成员函数 text ,它用于在图像上绘制文本。这个函数特别适用于 PIL(Python Imaging Library)环境,因为使用了 PIL 的 ImageDraw 模块来绘制文本。
# 定义 text 方法,它接受以下参数。
# 1.xy :一个元组 (x, y) ,表示文本的起始坐标。
# 2.text :要绘制的文本字符串。
# 3.txt_color :文本的颜色,默认为 (255, 255, 255) (白色)。
# 4.anchor :文本的锚点,默认为 'top' ,表示文本的起始点位于指定的 y 坐标。
def text(self, xy, text, txt_color=(255, 255, 255), anchor='top'):
# Add text to image (PIL-only) 在图像中添加文字(仅限 PIL)。
# 如果锚点 anchor 设置为 'bottom' ,则调整 y 坐标,使文本从字体的底部开始绘制。
if anchor == 'bottom': # start y from font bottom
# 获取文本的宽度和高度。
w, h = self.font.getsize(text) # text width, height
# 调整 y 坐标,使其从字体的底部开始绘制文本。
xy[1] += 1 - h
# 调用 PIL 的 ImageDraw 对象的 text 方法来绘制文本。这个方法接受以下参数。
# xy :文本的起始坐标。 text :要绘制的文本字符串。 fill :文本的颜色。 font :使用的字体。
self.draw.text(xy, text, fill=txt_color, font=self.font)
# 这个 text 方法提供了一个简单的方式来在图像上绘制文本,可以用于添加标签或注释。由于这个方法依赖于 PIL,因此只能在 PIL 环境下使用,而不能在 OpenCV 环境下使用。通过调整锚点,可以在不同的位置开始绘制文本,这对于精确控制文本位置非常有用。
# 这段代码定义了 Annotator 类的一个成员函数 fromarray ,它用于从 NumPy 数组更新 Annotator 实例的图像属性,并创建一个新的 ImageDraw.Draw 对象以便在图像上进行绘制。
# 定义 fromarray 方法,它接受一个参数。
# 1.im :这个参数是一个 NumPy 数组或 PIL 图像。
def fromarray(self, im):
# Update self.im from a numpy array 从 numpy 数组更新 self.im。
# 检查传入的 im 参数是否已经是 PIL 图像 ( Image.Image ) 的实例。如果是,直接将 im 赋值给 self.im 。如果不是,使用 Image.fromarray 方法将 NumPy 数组转换为 PIL 图像,并将结果赋值给 self.im 。
self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
# 创建一个 ImageDraw.Draw 对象,并将其赋值给 self.draw 。这个对象用于在 self.im 图像上绘制各种图形,如线条、矩形、文本等。
self.draw = ImageDraw.Draw(self.im)
# 这个 fromarray 方法提供了一个方便的方式来更新 Annotator 实例的内部图像表示,并确保始终有一个可用的 ImageDraw.Draw 对象来进行绘制操作。这对于在图像处理流程中动态更新图像内容非常有用,特别是在需要在图像上添加多个注释或图形时。
# 这段代码定义了 Annotator 类的一个成员函数 result ,它用于将经过注释的图像转换为 NumPy 数组并返回。
# 定义 result 方法,它不接受任何参数,只依赖类的实例本身。
def result(self):
# Return annotated image as array 以数组形式返回带注释的图像。
# 将 Annotator 类的 self.im 属性转换为 NumPy 数组。 self.im 属性是一个 PIL 图像, np.asarray() 函数用于从 PIL 图像创建一个 NumPy 数组。
return np.asarray(self.im)
# 这个 result 方法提供了一个简单的接口来获取经过注释的图像数据,使其可以用于进一步的处理或分析。这在图像注释和可视化任务中非常有用,尤其是在需要将图像数据传递给其他需要 NumPy 数组输入的函数或库时。
5.def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')):
# 这段代码定义了一个名为 feature_visualization 的函数,它用于可视化神经网络中某一层的特征图。
# 这是 feature_visualization 函数的定义,它接受以下参数。
# 1.x :要可视化的特征图张量。
# 2.module_type :产生特征图的模块类型。
# 3.stage :特征图所在的网络阶段。
# 4.n :要可视化的特征图数量,默认为32。
# 5.save_dir :保存可视化结果的目录,默认为 runs/detect/exp 。
def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')):
"""
x: Features to be visualized
module_type: Module type
stage: Module stage within model
n: Maximum number of feature maps to plot
save_dir: Directory to save results
"""
# 检查模块类型是否不包含 Detect ,如果不包含,则进行可视化操作。
if 'Detect' not in module_type:
# 获取特征图张量 x 的形状,包括批 次大小 、 通道数 、 高度 和 宽度 。
batch, channels, height, width = x.shape # batch, channels, height, width
# 确保特征图的高度和宽度都大于1,即特征图是有效的。
if height > 1 and width > 1:
# 构造保存可视化结果的文件名,文件名包含阶段和模块类型的信息。
f = save_dir / f"stage{stage}_{module_type.split('.')[-1]}_features.png" # filename
# 从批次中选择第一个样本( x[0] ),并按通道数将特征图分割成多个块。
blocks = torch.chunk(x[0].cpu(), channels, dim=0) # select batch index 0, block by channels
# 确定要可视化的特征图数量,不超过总通道数。
n = min(n, channels) # number of plots
# 创建一个子图网格,用于显示特征图。
# math.ceil 函数用于向上取整,确保即使不能整除8,也能有足够的行来容纳所有特征图。
fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols
# np.ndarray.ravel()
# ravel() 函数是 NumPy 库中的一个方法,它将多维数组(ndarray)展平成一维数组。这个方法不会复制数组的数据,而是返回一个新的视图(view),这个视图与原始数组共享相同的数据。
# 参数 :
# order :可选参数,指定展平的顺序。默认是 'C',表示按行主序(C-style),即按列优先顺序展平。如果设置为 'F',则表示按列主序(Fortran-style),即按行优先顺序展平。如果设置为 'A',则保持数组的原始顺序。
# 返回值 :
# 返回一个展平后的一维数组。
# 将子图网格展平,以便可以按顺序访问每个子图。
ax = ax.ravel()
# plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None, aspect=None)
# plt.subplots_adjust() 是 matplotlib 库中的一个函数,用于调整子图之间的间距以及子图与图形边缘之间的间距。这个函数允许用户自定义子图布局的参数,以便在显示多个子图时获得更好的视觉效果。
# 参数 :
# left :子图左边缘到图形左边缘的距离,以相对位置表示(0-1之间的值)。
# bottom :子图底部边缘到图形底部边缘的距离,以相对位置表示。
# right :子图右边缘到图形右边缘的距离,以相对位置表示。
# top :子图顶部边缘到图形顶部边缘的距离,以相对位置表示。
# wspace :子图之间的水平间距。可以是绝对值(如0.05英寸)或相对值(0-1之间的值)。
# hspace :子图之间的垂直间距。可以是绝对值或相对值。
# aspect :子图宽高比的调整因子。
# 功能描述 :
# plt.subplots_adjust() 函数的主要功能是微调子图的布局。通过调整上述参数,用户可以控制子图之间的间距以及子图与图形边缘之间的间距,从而优化图形的整体外观。
# 使用 plt.subplots_adjust() 可以帮助你创建更加清晰和专业的图形布局,特别是在你的图形包含多个子图时。
# 调整子图之间的间距。
plt.subplots_adjust(wspace=0.05, hspace=0.05)
# 遍历每个特征图块,将其显示在对应的子图上,并关闭坐标轴。
for i in range(n):
ax[i].imshow(blocks[i].squeeze()) # cmap='gray'
ax[i].axis('off')
# 记录保存可视化结果的日志信息。
LOGGER.info(f'Saving {f}... ({n}/{channels})') # 正在保存 {f}... ({n}/{channels})。
# 保存可视化结果为PNG文件。
plt.savefig(f, dpi=300, bbox_inches='tight')
# 关闭绘图窗口,释放资源。
plt.close()
# 将第一个样本的特征图数据保存为NumPy文件。
np.save(str(f.with_suffix('.npy')), x[0].cpu().numpy()) # npy save
# 这个函数的主要作用是将神经网络中某一层的特征图可视化,并保存为图像文件和NumPy文件,以便于分析和调试。通过可视化特征图,可以更好地理解模型在不同阶段的行为和特征提取能力。
6.def hist2d(x, y, n=100):
# 这段代码定义了一个名为 hist2d 的函数,用于计算二维直方图并返回对数变换后的值。这个函数可以用于可视化两个变量的联合分布,例如在目标检测中分析标签和预测之间的关系。
# 定义了一个名为 hist2d 的函数,接受三个参数。
# 1.x 和 2.y :是两个要计算直方图的数值数组。
# 3.n :是直方图的箱数,默认为100。
def hist2d(x, y, n=100):
# 2d histogram used in labels.png and evolve.png labels.png 和 evolve.png 中使用的 2d 直方图。
# 使用 np.linspace 函数分别计算 x 和 y 的值域,并生成 n 个等间距的点,这些点将作为直方图的边缘。 xedges 和 yedges 分别是 x 和 y 直方图的边界数组。
xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n)
# np.histogram2d(x, y, bins=10, range=None, density=None, weights=None)
# np.histogram2d 是 NumPy 库中的一个函数,用于计算两个数据集的二维直方图。这个函数可以处理两个输入数组,并返回它们在二维空间中的分布情况。
# 参数说明 :
# x :第一个数据数组。
# y :第二个数据数组,与 x 有相同的长度。
# bins :一个整数或一对整数。如果是一个整数,则 x 和 y 都将使用相同数量的箱子。如果是一对整数,则分别指定 x 和 y 的箱子数量。
# range :一个元组,形式为 ([x_min, x_max], [y_min, y_max]) ,用于指定直方图的值范围。如果没有指定,则使用 x 和 y 的最小值和最大值。
# density :布尔值,如果为 True ,则表示返回的直方图是归一化的,即每个箱子的高度表示密度而不是计数。
# weights :一个与 x 和 y 相同长度的数组,用于计算加权直方图。
# 返回值 :
# H :直方图的值,形状为 (bins[0], bins[1]) 。
# X : x 轴的箱子边缘值数组。
# Y : y 轴的箱子边缘值数组。
# 如果指定了 density 参数,返回的 H 数组将被归一化,使得 H 的所有值之和等于 1。如果没有指定 density ,则 H 的所有值之和等于输入数据点的总数。
# 使用 np.histogram2d 函数计算 x 和 y 的二维直方图。返回的 hist 是直方图的值, xedges 和 yedges 是直方图的边界。
hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges))
# np.digitize(x, bins, right=False)
# np.digitize 是 NumPy 库中的一个函数,它用于将一维数组中的元素映射到与之对应的箱子(bin)索引。这个函数常用于确定数据点落在哪个区间或箱子中,是直方图计算中的一个辅助步骤。
# 参数说明 :
# x :待确定箱子索引的一维数组。
# bins :定义箱子边界的一维数组。 x 中的元素将根据这些边界值被分配到不同的箱子中。
# right :布尔值,指定箱子的对齐方式。如果为 False ,则 x 中的值小于 bins[0] 时返回 0,大于等于 bins[-1] 时返回 len(bins) 。如果为 True ,则 x 中的值小于 bins[0] 时返回 1,大于等于 bins[-1] 时返回 len(bins) 。
# 返回值 :
# 返回一个整数数组,其中每个元素是 x 中对应元素的箱子索引。
# 使用 np.digitize 函数找出 x 中每个值所在的直方图箱的索引,然后减去1得到箱内的位置。 np.clip 函数确保索引值在合法范围内,即不小于0且不大于直方图高度减1。
xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1)
# 类似地,为 y 中的值找出直方图箱的索引,并确保索引值在合法范围内,这次是直方图宽度减1。
yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1)
# np.log(x, out=None, where=None, **kwargs)
# np.log 是 NumPy 库中的一个函数,用于计算输入数组中每个元素的自然对数(以数学常数 e 为底的对数)。这个函数可以处理标量、数组以及更复杂的数据结构,如矩阵。
# 参数说明 :
# x :输入值,可以是标量、数组或数组切片等,要求输入值必须大于0,因为对数函数在非正数上是未定义的。
# out :(可选)输出数组。如果提供,结果将存储在这个数组中。
# where :(可选)一个布尔数组,用于指定 x 中哪些元素需要计算对数。只有当 where 为 True 的位置对应的元素会被计算对数。
# **kwargs :(可选)其他关键字参数,用于指定函数的行为,例如处理复数输入时的分支切割等。
# 返回值 :
# 返回一个数组,其中包含输入数组 x 中每个元素的自然对数值。
# np.log 函数也可以处理复数输入,并且可以指定不同的底数,例如 : y = np.log(x, base=2)
# 使用 xidx 和 yidx 索引直方图 hist ,取出对应的值,并计算其自然对数。对数变换有助于处理直方图中值的非均匀分布,使得可视化更加清晰。
return np.log(hist[xidx, yidx])
# hist2d 函数通过计算 x 和 y 的二维直方图,并返回对数变换后的值,用于可视化两个变量的分布。这个函数在数据分析和可视化中非常有用,尤其是在处理目标检测模型的标签和预测时,可以帮助分析误差分布。
7.def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
# 这段代码定义了一个名为 butter_lowpass_filtfilt 的函数,它用于对数据应用巴特沃斯低通滤波器。巴特沃斯滤波器是一种平滑滤波器,可以让低频信号通过,同时阻止或减弱高频信号。
# 定义了一个名为 butter_lowpass_filtfilt 的函数,接受四个参数。
# 1.data :是要滤波的数据。
# 2.cutoff :是截止频率,默认为1500。
# 3.fs :是采样频率,默认为50000。
# 4.order :是滤波器的阶数,默认为5。
def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
# 从 scipy.signal 模块导入 butter 和 filtfilt 函数。 butter 用于设计滤波器, filtfilt 用于应用滤波器。
from scipy.signal import butter, filtfilt
# https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy
# 定义了一个内部函数 butter_lowpass ,用于生成巴特沃斯低通滤波器的系数。
def butter_lowpass(cutoff, fs, order):
# 计算奈奎斯特频率,它是采样频率的一半。
nyq = 0.5 * fs
# 将 截止频率 转换为相对于 奈奎斯特频率 的 标准化截止频率 。
normal_cutoff = cutoff / nyq
# 调用 butter 函数生成滤波器的系数。 order 是滤波器的阶数, normal_cutoff 是标准化的截止频率, btype='low' 指定为低通滤波器, analog=False 指定为数字滤波器。
return butter(order, normal_cutoff, btype='low', analog=False)
# 调用内部函数 butter_lowpass 生成滤波器的系数,并将其赋值给 b 和 a 。
b, a = butter_lowpass(cutoff, fs, order=order)
# 使用 filtfilt 函数对数据 data 应用滤波器。 filtfilt 是一个零相位滤波器,它通过前向和后向滤波来减少相位延迟。
return filtfilt(b, a, data) # forward-backward filter
# butter_lowpass_filtfilt 函数提供了一个方便的方式来对数据进行巴特沃斯低通滤波处理。通过内部定义滤波器设计函数和使用 filtfilt 函数,它能够对数据进行平滑处理,减少高频噪声的影响。这种滤波器在信号处理中非常常见,特别是在需要减少高频干扰或噪声时。
8.def output_to_target(output, max_det=300):
# 这段代码定义了一个名为 output_to_target 的函数,它将模型的输出转换为绘图所需的目标格式。
# 定义了一个名为 output_to_target 的函数,接受两个参数。
# 1.output :是模型的输出。
# 2.max_det :是每个批次中检测的最大数量,默认为300。
def output_to_target(output, max_det=300):
# Convert model output to target format [batch_id, class_id, x, y, w, h, conf] for plotting 将模型输出转换为目标格式 [batch_id, class_id, x, y, w, h, conf] 以进行绘图。
# 初始化一个空列表 targets ,用于存储转换后的目标格式数据。
targets = []
# 遍历 output 列表, i 是索引, o 是每个批次的输出。
for i, o in enumerate(output):
# 从每个批次的输出中选取前 max_det 个检测结果,并将其CPU上的张量分割成三个部分 : 边界框 box (4个值)、 置信度 conf (1个值)和 类别 cls (1个值)。 split((4, 1, 1), 1) 指定了每个部分的大小和分割的维度。
box, conf, cls = o[:max_det, :6].cpu().split((4, 1, 1), 1)
# 创建一个与 conf 相同长度的张量 j ,填充值为当前批次的索引 i 。这个张量将用于标识每个检测结果属于哪个批次。
j = torch.full((conf.shape[0], 1), i)
# 将 批次索引 j 、 类别 cls 、从 xyxy 格式转换为 xywh 格式的边界框 box (通过调用 xyxy2xywh 函数),以及 置信度 conf 连接起来,形成目标格式,并添加到 targets 列表中。
targets.append(torch.cat((j, cls, xyxy2xywh(box), conf), 1))
# 将 targets 列表中的所有张量连接起来,并转换为 NumPy 数组,然后返回。 torch.cat(targets, 0) 沿着第一个维度(批次维度)连接张量。
return torch.cat(targets, 0).numpy()
# output_to_target 函数将模型的输出转换为一个统一的格式,其中包括批次索引、类别、边界框和置信度,以便进行后续的绘图和分析。这个函数处理了多个批次的输出,并将它们合并成一个单一的 NumPy 数组,方便后续操作。
9.def plot_images(images, targets, paths=None, fname='images.jpg', names=None):
# 这段代码定义了一个名为 plot_images 的函数,用于将图像和目标(例如检测框和标签)绘制到一个网格中,并保存为一个图片文件。这个函数使用了多线程装饰器 @threaded 来提高处理效率。
# 这是一个装饰器,表示接下来的方法是线程安全的,可以在多线程环境中调用。
# def threaded(func):
# -> threaded 的装饰器,它用于将一个函数转换为多线程执行。wrapper 函数返回创建的线程对象,这样调用者可以对线程进行进一步的操作,比如等待线程结束。装饰器 threaded 返回 wrapper 函数,这样当 @threaded 装饰器应用到一个函数上时,实际上是用 wrapper 函数包装了原始函数。
# -> return wrapper
@threaded
# 定义了一个名为 plot_images 的函数,接受五个参数。
# 1.images :是要绘制的图像批次。
# 2.targets :是对应的目标(例如检测结果)。
# 3.paths :是图像的路径(可选)。
# 4.fname :是输出文件的名称,默认为 'images.jpg'。
# 5.names :是类别名称的列表(可选)。
def plot_images(images, targets, paths=None, fname='images.jpg', names=None):
# 这段代码是 plot_images 函数的一部分,它负责将输入的图像数据和目标数据转换成适合绘图的格式,并进行一些预处理。
# Plot image grid with labels 用标签绘制图像网格。
# 检查 images 是否为 PyTorch 张量。如果是,执行以下操作。
if isinstance(images, torch.Tensor):
# 将图像张量移动到 CPU,转换为浮点数类型,并转换为 NumPy 数组。这是因为 PyTorch 张量需要被转换为 NumPy 数组才能使用 OpenCV 或其他图像处理库进行操作。
images = images.cpu().float().numpy()
# 检查 targets 是否为 PyTorch 张量。如果是,执行以下操作。
if isinstance(targets, torch.Tensor):
# 将目标张量移动到 CPU 并转换为 NumPy 数组。
targets = targets.cpu().numpy()
# 定义一个常量 max_size ,表示单个图像的最大尺寸,单位为像素。
max_size = 1920 # max image size
# 定义一个常量 max_subplots ,表示在图像网格中最多显示的子图数量,这里设置为 16,即 4x4 网格。
max_subplots = 16 # max image subplots, i.e. 4x4
# 获取图像数据的形状,包括批次大小 bs 、高度 h 和宽度 w 。
bs, _, h, w = images.shape # batch size, _, height, width
# 将批次大小限制为 max_subplots ,以确保不会尝试绘制超过网格大小的图像数量。
bs = min(bs, max_subplots) # limit plot images
# 计算图像网格的行数和列数,这里使用批次大小的平方根,并向上取整,以形成一个接近正方形的网格。
ns = np.ceil(bs ** 0.5) # number of subplots (square)
# 检查图像数据是否归一化到 [0, 1] 范围内。
if np.max(images[0]) <= 1:
# 如果图像数据归一化到 [0, 1],则将其反归一化到 [0, 255] 范围,以便用于图像显示和保存。这一步是可选的,取决于图像数据的原始范围。
images *= 255 # de-normalise (optional)
# 这段代码的目的是准备图像和目标数据,以便进行绘图。它确保了数据在正确的格式(NumPy 数组),并且处理了图像的归一化问题。此外,它还计算了用于绘制图像的网格大小,确保不会超出设定的最大子图数量。这些步骤是绘制图像网格和添加标签之前的重要预处理步骤。
# 这段代码继续实现了 plot_images 函数,负责构建一个图像网格(mosaic),将多个图像排列在一个网格中。
# Build Image
# 创建一个空白的网格图像 mosaic ,其大小为 ns * h 乘以 ns * w 像素,通道数为3(RGB),初始填充值为255(白色)。 ns 是网格的行数和列数, h 和 w 是每个子图的高度和宽度。
mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8) # init
# 遍历 images 数组中的每个图像, i 是索引, im 是图像数据。
for i, im in enumerate(images):
# 检查是否已经处理了最大子图数量 max_subplots 。
if i == max_subplots: # if last batch has fewer images than we expect
# 如果已经处理了最大子图数量,则退出循环。
break
# 计算当前图像在网格中的位置。 i // ns 计算当前图像所在的行, i % ns 计算当前图像所在的列。然后分别乘以每个子图的宽度和高度,得到左上角的坐标 (x, y) 。
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
# 调整当前图像 im 的维度顺序,从 (通道, 高度, 宽度) 转换为 (高度, 宽度, 通道) ,以符合 OpenCV 的图像格式要求。
im = im.transpose(1, 2, 0)
# 将调整后的图像 im 放置到网格 mosaic 的相应位置。 y:y + h 和 x:x + w 定义了图像在网格中的区域。
mosaic[y:y + h, x:x + w, :] = im
# 这段代码负责构建一个图像网格,将多个图像按照计算出的位置排列在一个空白画布上。这个过程涉及到初始化一个空白网格,然后遍历每个图像,将其放置到网格的相应位置。这样,就可以将多个图像组合成一个大的图像,用于后续的显示或保存。
# 这段代码是 plot_images 函数中用于可选调整图像大小的部分。
# Resize (optional)
# 计算缩放比例 scale 。这个比例是基于最大图像尺寸 max_size 、网格中的子图数量 ns 以及单个子图的最大尺寸(高度 h 或宽度 w 中的较大者)来确定的。如果 scale 小于 1,意味着网格图像的尺寸超过了 max_size ,需要缩小。
scale = max_size / ns / max(h, w)
# 检查缩放比例是否小于 1,如果是,则需要对图像进行缩放。
if scale < 1:
# 根据缩放比例计算新的网格高度,并使用 math.ceil 函数向上取整,以确保尺寸是整数。
h = math.ceil(scale * h)
# 根据缩放比例计算新的网格宽度,并使用 math.ceil 函数向上取整。
w = math.ceil(scale * w)
# 使用 OpenCV 的 cv2.resize 函数对网格图像 mosaic 进行缩放。新的大小由 (w, h) 确定,即新的网格宽度和高度。 tuple(int(x * ns) for x in (w, h)) 创建一个元组,包含缩放后的宽度和高度,每个值都乘以 ns (网格中的子图数量),以确保整个网格被缩放。
mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h)))
# 这段代码提供了一个可选步骤,用于调整图像网格的大小,使其不超过设定的最大尺寸 max_size 。通过计算缩放比例并应用到网格图像上,可以确保最终的图像网格在视觉上不会过大,同时保持了图像的清晰度。这个过程是可选的,取决于是否需要对图像网格的大小进行限制。
# 这段代码是 plot_images 函数中用于在图像网格上添加注释的部分,包括边界框、类别标签和文件名。
# Annotate
# 计算字体大小 fs ,基于图像的高度和宽度以及网格中的子图数量来动态调整。
fs = int((h + w) * ns * 0.01) # font size
# 创建一个 Annotator 对象,用于在图像网格上绘制注释。 line_width 是线条宽度, font_size 是字体大小, pil=True 表示使用 PIL(Python Imaging Library)进行图像处理, example=names 提供类别名称的示例。
annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True, example=names)
# 遍历每个图像, i 是图像的索引。
for i in range(i + 1):
# 计算每个图像在网格中的左上角坐标。
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
# 在每个图像周围绘制一个白色的矩形框, width=2 指定边框宽度。
# def rectangle(self, xy, fill=None, outline=None, width=1): -> 用于在图像上绘制矩形。这个函数特别适用于 PIL(Python Imaging Library)环境,因为使用了 PIL 的 ImageDraw 模块来绘制图形。
annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2) # borders
# 如果提供了图像路径,则执行以下操作。
if paths:
# 在每个图像左上角绘制文件名,文本颜色为浅灰色。
# def text(self, xy, text, txt_color=(255, 255, 255), anchor='top'): -> 用于在图像上绘制文本。这个函数特别适用于 PIL(Python Imaging Library)环境,因为使用了 PIL 的 ImageDraw 模块来绘制文本。
annotator.text((x + 5, y + 5), text=Path(paths[i]).name[:40], txt_color=(220, 220, 220)) # filenames
# 如果提供了目标数据,则执行以下操作。
if len(targets) > 0:
# 筛选出当前图像的目标数据。 [batch_id, class_id, x, y, w, h, conf]
ti = targets[targets[:, 0] == i] # image targets
# 将目标的边界框从中心点和宽高格式转换为左上角和右下角坐标格式。
boxes = xywh2xyxy(ti[:, 2:6]).T
# 获取目标的类别。
classes = ti[:, 1].astype('int')
# 检查目标数据是否包含置信度列。
labels = ti.shape[1] == 6 # labels if no conf column
# 如果目标数据不包含置信度列,则 conf 为 None ;否则,获取置信度值。
conf = None if labels else ti[:, 6] # check for confidence presence (label vs pred)
# 如果边界框不为空,则执行以下操作。
if boxes.shape[1]:
# 如果边界框坐标归一化到 [0, 1] 范围内,则执行以下操作。
if boxes.max() <= 1.01: # if normalized with tolerance 0.01
# 将边界框的 x 和宽度坐标缩放到像素值。
boxes[[0, 2]] *= w # scale to pixels
# 将边界框的 y 和高度坐标缩放到像素值。
boxes[[1, 3]] *= h
# 如果图像被缩放,则执行以下操作。
elif scale < 1: # absolute coords need scale if image scales
# 将边界框坐标按缩放比例调整。
boxes *= scale
# 调整边界框的 x 和宽度坐标,使其适应网格图像。
boxes[[0, 2]] += x
# 调整边界框的 y 和高度坐标,使其适应网格图像。
boxes[[1, 3]] += y
# 遍历每个边界框,绘制边界框和标签。
for j, box in enumerate(boxes.T.tolist()):
# 获取当前边界框的类别。
cls = classes[j]
# 根据类别获取对应的颜色。
color = colors(cls)
# 如果提供了类别名称,则使用名称;否则,使用类别索引。
cls = names[cls] if names else cls
# 如果目标数据包含置信度列,或者置信度值大于 0.25,则执行以下操作。
if labels or conf[j] > 0.25: # 0.25 conf thresh
# 构造标签文本,如果包含置信度,则显示类别和置信度值。
label = f'{cls}' if labels else f'{cls} {conf[j]:.1f}'
# 在图像上绘制边界框和标签。
# def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)): -> 用于在图像上绘制边界框并添加文本标签。这个函数根据类是否使用 PIL 或 OpenCV 来绘制不同的图形。
annotator.box_label(box, label, color=color)
# 保存带有注释的图像网格到文件 fname 。
annotator.im.save(fname) # save
# 这段代码负责在图像网格上添加注释,包括边界框、类别标签和文件名。它使用 Annotator 类来处理注释的绘制,并根据目标数据中的信息来确定哪些边界框和标签需要绘制。这个过程使得图像网格不仅显示图像,还提供了关于每个图像内容的额外信息,方便进行可视化分析。
# plot_images 函数用于将一批图像和对应的目标(例如检测框和标签)绘制到一个网格中,并保存为一个图片文件。这个函数处理了图像的反归一化、缩放、边界框的格式转换和绘制,以及类别标签的绘制。它提供了一个方便的方式来可视化模型的检测结果。
10.def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
# 这段代码定义了一个名为 plot_lr_scheduler 的函数,用于绘制学习率调度器(LR Scheduler)在训练过程中的学习率变化。
# 定义了一个名为 plot_lr_scheduler 的函数,接受四个参数。
# 1.optimizer :优化器。
# 2.scheduler :学习率调度器。
# 3.epochs :训练的总轮数,默认为300。
# 4.save_dir :保存图像的目录,默认为空字符串。
def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
# Plot LR simulating training for full epochs 绘制完整 epoch 的 LR 模拟训练图。
# 使用 copy 模块创建 优化器 和 调度器 的副本,以避免在原始对象上进行修改。
optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals
# 初始化一个空列表 y ,用于存储每个epoch后的学习率值。
y = []
# 遍历指定的训练轮数 epochs 。
for _ in range(epochs):
# torch.optim.Optimizer.step()
# 在 PyTorch 中, step() 函数是优化器(Optimizer)类的一个方法,用于执行单步参数更新。这个方法会根据优化器内部的参数和学习率调度器来更新模型的参数。
# 参数说明 :无参数。
# 返回值 :无返回值。
# step() 方法通常在每次迭代后调用,它会对所有之前通过 zero_grad() 方法清零的参数进行梯度更新。在调用 step() 之前,需要确保已经计算了模型的梯度(通过反向传播)并且清零了之前的梯度。
# 需要注意的是, step() 方法不应该在没有梯度的情况下被调用,因为这可能会导致错误或未定义的行为。
# 此外,如果你使用了学习率调度器(LR Scheduler),通常在 step() 调用之后立即更新学习率 :
# scheduler.step() # 在每个 epoch 结束时更新学习率。
# 这里的 scheduler.step() 会根据学习率调度器的策略更新学习率,为下一个 epoch 的训练做好准备。
# 模拟每个epoch结束后调度器的步骤,这通常会更新学习率。
scheduler.step()
# 将当前的学习率(第一个参数组的学习率)添加到列表 y 中。
y.append(optimizer.param_groups[0]['lr'])
# 使用 Matplotlib 的 plt.plot 函数绘制学习率变化曲线, '.-' 指定了点和线的样式, label='LR' 为曲线设置标签。
plt.plot(y, '.-', label='LR')
# 设置 x 轴的标签为 'epoch'。
plt.xlabel('epoch')
# 设置 y 轴的标签为 'LR'(Learning Rate)。
plt.ylabel('LR')
# 调用 plt.grid 函数添加网格线,以便更好地阅读图表。
plt.grid()
# 设置 x 轴的范围从 0 到 epochs 。
plt.xlim(0, epochs)
# 设置 y 轴的下限为 0。
plt.ylim(0)
# 将绘制的图表保存为 PNG 文件,文件名为 'LR.png',保存在 save_dir 指定的目录下,设置分辨率为 200 dpi。
plt.savefig(Path(save_dir) / 'LR.png', dpi=200)
# 关闭图表,释放资源。
plt.close()
# plot_lr_scheduler 函数模拟了整个训练过程中学习率的变化,并将其绘制成图表。这个函数对于理解学习率调度器的行为和调试训练过程非常有用。通过可视化学习率的变化,可以更好地调整调度器的参数,以达到最佳的训练效果。
11.def plot_val_txt():
# 这段代码定义了一个名为 plot_val_txt 的函数,用于从 val.txt 文件中读取数据并绘制直方图。
# 定义了一个名为 plot_val_txt 的函数,没有参数。
def plot_val_txt(): # from utils.plots import *; plot_val()
# Plot val.txt histograms 绘制 val.txt 直方图。
# np.loadtxt(fname, dtype=float, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0)
# np.loadtxt 是 NumPy 库中的一个函数,用于从文本文件中加载数据。这个函数可以读取文本文件中的数值数据,并将它们转换为 NumPy 数组。它提供了灵活的参数设置,以适应不同的数据格式和需求。
# 参数说明 :
# fname :文件名或文件对象。可以是字符串路径、文件路径列表或类似文件的对象。
# dtype :数据类型,默认为 float 。指定加载数据的期望数据类型。
# comments :注释字符,默认为 '#' 。指定用作注释的字符,注释行将被忽略。
# delimiter :分隔符,默认为 None 。指定字段的分隔符,如果为 None ,则自动检测空白字符作为分隔符。
# converters :转换函数字典,默认为 None 。为每列数据提供自定义的转换函数。
# skiprows :跳过的行数,默认为 0 。指定在开始读取数据之前要跳过的行数。
# usecols :使用列的索引或范围,默认为 None 。指定要读取的列。
# unpack :布尔值,默认为 False 。如果为 True ,则返回一个元组,其中包含解包的数组。
# ndmin :输出数组的最小维度,默认为 0 。 0 表示返回一维数组, 1 表示返回二维数组,即使只有一列数据。
# 返回值 :
# 返回一个 NumPy 数组,其形状和数据类型取决于输入文件和参数。
# 异常 :
# 如果文件无法读取或数据格式不正确,可能会抛出异常。
# np.loadtxt 是处理文本数据文件的常用函数,特别适用于那些由数值数据组成的文件,如 CSV 文件。它为数据加载和预处理提供了一个简单而有效的方法。
# 使用 NumPy 的 loadtxt 函数从 val.txt 文件中加载数据,数据类型为 np.float32 。
x = np.loadtxt('val.txt', dtype=np.float32)
# 将读取的数据中的边界框从 (x1, y1, x2, y2) 格式转换为 (x_center, y_center, width, height) 格式。这里假设 x 的前四列是边界框的坐标。
box = xyxy2xywh(x[:, :4])
# 从转换后的边界框中提取中心点的 x 坐标和 y 坐标。
cx, cy = box[:, 0], box[:, 1]
# 使用 Matplotlib 创建一个图形和一个子图,设置图形大小为 6x6 英寸,并启用紧凑布局。
fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True)
# matplotlib.pyplot.hist2d(x, y, bins=10, range=None, density=False, weights=None, cmin=None, cmax=None, *, data=None, **kwargs)
# hist2d 是 Matplotlib 库中 pyplot 模块的一个函数,用于绘制二维直方图。这个函数可以帮助我们可视化两个变量的联合分布情况,通常用于观察两个变量之间的关系。
# 参数说明 :
# x , y :数据序列,即要绘制二维直方图的两个变量的值。
# bins :可选参数,定义了直方图的箱数(bins)。可以是整数、序列或字符串。
# 如果是整数,表示两个维度的箱数相同( nx = ny = bins )。 如果是 [int, int] ,表示每个维度的箱数( nx, ny = bins )。
# 如果是数组类型,表示两个维度的箱边缘( x_edges = y_edges = bins )。 如果是 [array, array] ,表示每个维度的箱边缘( x_edges, y_edges = bins )。 默认值为 10。
# range :可选参数,表示每个维度的箱的上下范围( [[xmin, xmax], [ymin, ymax]] )。如果未在 bins 参数中明确指定,则使用此范围。超出此范围的值将被视为异常值,不计入直方图。
# density :可选参数,布尔值,表示是否归一化直方图。如果为 True ,则直方图的面积总和为 1。
# weights :可选参数,权重数组,形状与 x 相同,用于计算加权直方图。
# cmin , cmax :可选参数,用于指定直方图的显示范围。小于 cmin 或大于 cmax 的箱子不会被显示,并在返回的直方图数据中设置为 NaN 。
# 返回值 :
# h :二维数组,表示样本 x 和 y 的二维直方图。
# xedges :一维数组,表示沿 x 轴的箱边缘。
# yedges :一维数组,表示沿 y 轴的箱边缘。
# image : QuadMesh 对象,用于绘制直方图。
# 在子图上绘制 cx 和 cy 的二维直方图,设置直方图的箱体数量为 600,颜色最大值为 10,最小值为 0。
ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0)
# 设置子图的纵横比为相等,使得 x 轴和 y 轴的单位长度相同。
ax.set_aspect('equal')
# 将绘制的二维直方图保存为 PNG 文件,文件名为 'hist2d.png',设置分辨率为 300 dpi。
plt.savefig('hist2d.png', dpi=300)
# 创建一个新的图形和两个子图,设置图形大小为 12x6 英寸,并启用紧凑布局。
fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
# 在第一个子图上绘制 cx 的一维直方图,设置直方图的箱体数量为 600。
ax[0].hist(cx, bins=600)
# 在第二个子图上绘制 cy 的一维直方图,设置直方图的箱体数量为 600。
ax[1].hist(cy, bins=600)
# 将绘制的一维直方图保存为 PNG 文件,文件名为 'hist1d.png',设置分辨率为 200 dpi。
plt.savefig('hist1d.png', dpi=200)
# plot_val_txt 函数从 val.txt 文件中读取边界框数据,转换边界框格式,并绘制中心点的二维和一维直方图。这个函数用于可视化边界框的分布情况,帮助分析数据集中对象的位置特征。
12.def plot_targets_txt():
# 这段代码定义了一个名为 plot_targets_txt 的函数,用于从 targets.txt 文件中读取目标数据并绘制直方图。
# 定义了一个名为 plot_targets_txt 的函数,没有参数。
def plot_targets_txt(): # from utils.plots import *; plot_targets_txt()
# Plot targets.txt histograms 绘制 targets.txt 直方图。
# 使用 NumPy 的 loadtxt 函数从 targets.txt 文件中加载数据,数据类型为 np.float32 ,并转置数组,使得每一列代表一个特征(例如,x, y, width, height)。
x = np.loadtxt('targets.txt', dtype=np.float32).T
# 定义一个列表 s ,包含四个字符串,分别对应于四个特征的名称。
s = ['x targets', 'y targets', 'width targets', 'height targets']
# 使用 Matplotlib 创建一个图形和四个子图(2x2布局),设置图形大小为 8x8 英寸,并启用紧凑布局。
fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
# 将子图数组 ax 展平,使其成为一个一维数组,方便遍历。
ax = ax.ravel()
# 遍历四个特征。
for i in range(4):
# 在每个子图上绘制对应特征的直方图,设置直方图的箱体数量为 100,并添加图例,显示均值和标准差。
ax[i].hist(x[i], bins=100, label=f'{x[i].mean():.3g} +/- {x[i].std():.3g}')
# 显示每个子图的图例。
ax[i].legend()
# 为每个子图设置标题,使用列表 s 中的字符串。
ax[i].set_title(s[i])
# 将绘制的图形保存为 JPG 文件,文件名为 'targets.jpg',设置分辨率为 200 dpi。
plt.savefig('targets.jpg', dpi=200)
# plot_targets_txt 函数从 targets.txt 文件中读取目标数据,绘制每个特征的直方图,并显示均值和标准差。这个函数用于可视化目标数据的分布情况,帮助分析数据集中目标的位置和尺寸特征。通过四个子图,可以直观地比较不同特征的分布差异。
13.def plot_val_study(file='', dir='', x=None):
# 这段代码定义了一个名为 plot_val_study 的函数,用于绘制由 val.py 生成的 study.txt 文件中的性能研究数据(或在指定目录中绘制所有 study*.txt 文件的数据)。
# 定义了一个名为 plot_val_study 的函数,接受三个参数。
# 1.file :要绘制的文件名。
# 2.dir :包含 study*.txt 文件的目录。
# 3.x :可选的 x 轴数据。
def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_val_study()
# Plot file=study.txt generated by val.py (or plot all study*.txt in dir) 绘图 file = 由 val.py 生成的 study.txt (或绘制目录中的所有 study*.txt)。
# 如果提供了 file 参数,则使用文件的父目录作为保存目录;否则,使用 dir 参数作为保存目录。
save_dir = Path(file).parent if file else Path(dir)
# 定义一个布尔变量 plot2 ,用于控制是否绘制额外的结果。
plot2 = False # plot additional results
# 如果 plot2 为 True ,则执行以下操作。
if plot2:
# 创建一个 2x4 的子图布局,并获取第二行的子图数组。
ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)[1].ravel()
# 创建一个新的图形和一个子图,用于绘制第二个图表。
fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
# for f in [save_dir / f'study_coco_{x}.txt' for x in ['yolov5n6', 'yolov5s6', 'yolov5m6', 'yolov5l6', 'yolov5x6']]:
# 遍历保存目录中所有以 study 开头的 .txt 文件。
for f in sorted(save_dir.glob('study*.txt')):
# 从每个文件中加载数据,只选择特定的列,并转置数组。
y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
# 如果未提供 x 参数,则使用列索引作为 x 轴数据;否则,使用提供的 x 数据。
x = np.arange(y.shape[1]) if x is None else np.array(x)
# 如果 plot2 为 True ,则执行以下操作。
if plot2:
# 定义一个列表 s ,包含每个数据列的名称。
s = ['P', 'R', 'mAP@.5', 'mAP@.5:.95', 't_preprocess (ms/img)', 't_inference (ms/img)', 't_NMS (ms/img)']
# 遍历每个数据列。
for i in range(7):
# 在对应的子图上绘制数据。
ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
# 设置子图的标题。
ax[i].set_title(s[i])
# 找到 y[3] (mAP@.5)列中最大值的索引,并加 1。
j = y[3].argmax() + 1
# 在第二个图表上绘制处理时间与 mAP@.5 的关系。
ax2.plot(y[5, 1:j],
y[3, 1:j] * 1E2,
'.-',
linewidth=2,
markersize=8,
label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
# 在第二个图表上绘制 EfficientDet 的性能数据。
ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
'k.-',
linewidth=2,
markersize=8,
alpha=.25,
label='EfficientDet')
# 在第二个图表上添加网格线。
ax2.grid(alpha=0.2)
# 设置第二个图表的 y 轴刻度。
ax2.set_yticks(np.arange(20, 60, 5))
# 设置第二个图表的 x 轴范围。
ax2.set_xlim(0, 57)
# 设置第二个图表的 y 轴范围。
ax2.set_ylim(25, 55)
# 设置第二个图表的 x 轴标签。
ax2.set_xlabel('GPU Speed (ms/img)')
# 设置第二个图表的 y 轴标签。
ax2.set_ylabel('COCO AP val')
# 在第二个图表上显示图例。
ax2.legend(loc='lower right')
# 定义保存文件的路径。
f = save_dir / 'study.png'
# 打印保存文件的信息。
print(f'Saving {f}...')
# 将图表保存为 PNG 文件,设置分辨率为 300 dpi。
plt.savefig(f, dpi=300)
# plot_val_study 函数用于绘制性能研究数据,包括精确度、召回率、平均精度和处理时间等指标。它可以根据提供的文件或目录中的文件绘制多个图表,并将结果保存为图像文件。这个函数对于分析模型的性能和处理时间非常有用。
14.def plot_labels(labels, names=(), save_dir=Path('')):
# 这段代码定义了一个名为 plot_labels 的函数,用于绘制数据集中的标签分布情况。这个函数使用 Seaborn 和 Matplotlib 库来创建多个图表,包括相关图、直方图和边界框图。
# 这是一个装饰器,用于捕获函数执行过程中可能出现的任何异常,并打印错误信息。
# class TryExcept(contextlib.ContextDecorator):
# -> 这个上下文管理器的作用是在代码块执行过程中捕获异常,并在异常发生时打印一条包含自定义消息和异常值的消息。
# -> def __init__(self, msg=''):
@TryExcept() # known issue https://github.com/ultralytics/yolov5/issues/5395
# 定义了一个名为 plot_labels 的函数,接受三个参数。
# 1.labels :包含标签数据的数组。
# 2.names :类别名称的字典或列表,默认为空。
# 3.save_dir :保存图像的目录,默认为当前目录。
def plot_labels(labels, names=(), save_dir=Path('')):
# plot dataset labels
# 使用 LOGGER 记录信息,表明正在将标签绘制到指定的路径。
LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ") # 将标签绘制到 {save_dir / 'labels.jpg'}...
# 从 labels 数组中分离出类别标签 c 和边界框 b 。
c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
# 计算类别的数量。
nc = int(c.max() + 1) # number of classes
# pd.DataFrame(data, index=None, columns=None, dtype=None, copy=False)
# pd.DataFrame 是 Pandas 库中的一个构造函数,用于创建 DataFrame 对象。DataFrame 是 Pandas 中最核心的数据结构之一,它提供了一个二维标签化数据结构,可以存储不同类型的数据,如数值、字符串、布尔值等。
# 参数说明 :
# data :这是创建 DataFrame 时必须提供的数据。 data 可以是以下几种类型之一 :
# 一个字典,其中键是列名,值是数据列表或数组。
# 一个二维 NumPy 数组或类似数组的结构。
# 另一个 DataFrame 或相似的表格型数据结构。
# 一个列表或列表的列表。
# index :(可选)索引标签,可以是索引对象或数组。如果未指定,Pandas 将自动创建一个从 0 开始的整数索引。
# columns :(可选)列名,可以是列名列表。如果未指定且 data 是字典,则使用字典的键作为列名。
# dtype :(可选)数据类型,用于强制 DataFrame 中的数据类型。如果未指定,Pandas 将自动推断数据类型。
# copy :(可选)布尔值,指示是否在创建 DataFrame 时复制数据。默认为 False,即不复制数据。
# 返回值 :
# 返回一个 DataFrame 对象。
# 将边界框数据转换为 Pandas DataFrame。
x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
# seaborn correlogram
# sns.pairplot(data, hue=None, vars=None, x_vars=None, y_vars=None, kind='scatter', diag_kind='hist', plot_kws=None, diag_kws=None, hue_kws=None, palette=None, dropna=True, vars_order=None, x_vars_order=None, y_vars_order=None, height=2.5, aspect=1, corner=True, join=False, scatter_kws=None, line_kws=None, kind_order=None)
# sns.pairplot 是 Seaborn 库中的一个函数,用于创建一个矩阵式的图表,显示数据集中所有变量之间的成对关系。这个函数特别适合于探索数据集中变量之间的关系,尤其是当你想要快速查看不同变量之间的分布和关系时。
# 参数说明 :
# data :要绘制的数据,通常是一个 pandas DataFrame。
# hue :(可选)指定用于颜色编码的列名,用于在图表中显示不同类别的数据。
# vars :(可选)要绘制的变量列表。
# x_vars 、 y_vars :(可选)分别指定 x 轴和 y 轴上的变量列表。
# kind :(可选)图表的类型,可以是 'scatter'(散点图)、'reg'(回归线图)、'resid'(残差图)、'kde'(核密度估计图)等。
# diag_kind :(可选)对角线上图表的类型,通常是 'hist'(直方图)。
# plot_kws 、 diag_kws 、 hue_kws :(可选)传递给 图表 、 对角线图表 和 颜色编码图表 的关键字参数。
# palette :(可选)颜色映射。
# dropna :(可选)是否丢弃包含缺失值的行。
# vars_order 、 x_vars_order 、 y_vars_order :(可选)变量的顺序。
# height :(可选)每个子图的高度。
# aspect :(可选)每个子图的宽高比。
# corner :(可选)是否在图表的角落显示对角线上的图表。
# join :(可选)是否在散点图中连接点。
# scatter_kws 、 line_kws :(可选)传递给散点图和线图的关键字参数。
# kind_order :(可选)图表类型的顺序。
# 返回值 :
# 返回一个 PairGrid 对象,可以进一步定制图表。
# 使用 Seaborn 的 pairplot 函数绘制边界框数据的相关图。
# 这行代码使用了 Seaborn 库的 pairplot 函数来创建一个成对关系的图,通常用于展示变量之间的关系,包括直方图和散点图。
# sn.pairplot(x, corner=True, diag_kind='auto', kind='hist', diag_kws=dict(bins=50), plot_kws=dict(pmax=0.9)) :这个命令创建了一个成对关系的图,其中 x 是输入的 Pandas DataFrame,包含了要分析的变量。
# corner=True :这个参数设置为 True 表示在图的对角线上显示直方图,这有助于展示单个变量的分布情况。
# diag_kind='auto' :这个参数设置为 'auto' 表示自动选择对角线上的图的类型。在这种情况下,由于 corner=True ,对角线上将显示直方图。
# kind='hist' :这个参数设置非对角线上的图为直方图,用于展示两个变量之间的关系。
# diag_kws=dict(bins=50) :这个参数是一个字典,用于传递给对角线上直方图的关键字参数。在这里, bins=50 表示直方图的箱体数量为 50。
# plot_kws=dict(pmax=0.9) :这个参数也是一个字典,用于传递给非对角线上直方图的关键字参数。在这里, pmax=0.9 表示直方图显示的最大值为 90% 的数据范围。
# 使用 Seaborn 的 pairplot 函数创建了一个包含直方图和散点图的成对关系图,用于可视化 DataFrame x 中变量之间的关系。这种类型的图特别适用于探索性数据分析,可以帮助识别变量之间的潜在关系和分布特征。
sn.pairplot(x, corner=True, diag_kind='auto', kind='hist', diag_kws=dict(bins=50), plot_kws=dict(pmax=0.9))
# 保存相关图为 JPG 文件。
plt.savefig(save_dir / 'labels_correlogram.jpg', dpi=200)
# 关闭当前的绘图窗口。
plt.close()
# matplotlib labels
# 设置 Matplotlib 使用 SVG 后端,以加快绘图速度。
matplotlib.use('svg') # faster
# 创建一个 2x2 的子图布局,并获取第二行的子图数组。
ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel()
# 在第一个子图上绘制 类别标签 的直方图。
y = ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8)
# 使用 contextlib.suppress 忽略异常,用于尝试给直方图的条形上色。
with contextlib.suppress(Exception): # color histogram bars by class
# 尝试根据类别给直方图的条形上色。
[y[2].patches[i].set_color([x / 255 for x in colors(i)]) for i in range(nc)] # known issue #3195
# 设置第一个子图的 y 轴标签。
ax[0].set_ylabel('instances')
# 如果 names 列表的长度在 1 到 30 之间,则执行以下操作。
if 0 < len(names) < 30:
# 设置 x 轴的刻度。
ax[0].set_xticks(range(len(names)))
# 设置 x 轴的刻度标签。
ax[0].set_xticklabels(list(names.values()), rotation=90, fontsize=10)
else:
ax[0].set_xlabel('classes')
# sns.histplot(data=None, *, x=None, y=None, hue=None, weights=None, stat='count', bins='auto', binwidth=None, binrange=None, discrete=None, cumulative=False, common_bins=True, common_norm=True, multiple='layer', element='bars', fill=True, shrink=1, kde=False, kde_kws=None, line_kws=None, thresh=0, pthresh=None, pmax=None, cbar=False, cbar_ax=None, cbar_kws=None, palette=None, hue_order=None, hue_norm=None, color=None, log_scale=None, legend=True, ax=None, **kwargs)
# sns.histplot 是 Seaborn 库中的一个函数,用于绘制单变量或双变量的直方图,以展示数据集的分布情况。这个函数可以自定义直方图的多种属性,包括bins的数量、宽度、范围,以及是否显示累积分布等。
# 参数说明 :
# data :输入数据结构,可以是 pandas DataFrame、NumPy 数组、映射或序列。
# x 、 y :在 x 轴和 y 轴上表示的变量,可以是数据中的列名或键。
# hue :用于颜色编码的语义变量,用于在同一图表中显示不同类别的数据。
# weights :权重向量,用于调整每个数据点对每个 bin 计数的贡献。
# stat :在每个 bin 内计算的聚合统计量,如 'count'、'frequency'、'probability'、'percent'、'density' 等。
# bins :bins的数量或bins的边界,可以是整数、字符串(参考规则名)、边界向量或边界对。
# binwidth :每个 bin 的宽度,可以与 binrange 一起使用。
# binrange :bin 边界的范围,可以是数字对或数字对的对。
# discrete :如果为 True,则将数据视为离散数据,并相应地调整 bins。
# cumulative :如果为 True,则绘制累积计数。
# common_bins :如果为 True,则在多个直方图之间使用相同的 bins。
# common_norm :如果为 True,则在归一化统计时使用整个数据集。
# multiple :处理多个元素的策略,可以是 'layer'、'dodge'、'stack' 或 'fill'。
# element :直方图的元素类型,可以是 'bars'、'step' 或 'poly'。
# fill :是否填充直方图的元素。
# shrink :bins 宽度的缩放因子。
# kde :是否添加核密度估计(KDE)曲线。
# kde_kws :传递给 KDE 曲线的关键字参数。
# line_kws :传递给线条的关键字参数。
# thresh 、 pthresh 、 pmax :用于控制直方图显示的阈值参数。
# cbar :是否显示颜色条。
# cbar_ax 、 cbar_kws :颜色条的轴和关键字参数。
# palette :颜色映射。
# hue_order 、 hue_norm :hue 变量的顺序和归一化。
# color :单一颜色规范,当不使用 hue 映射时使用。
# log_scale :是否将轴设置为对数刻度。
# legend :是否显示图例。
# ax :预存在的轴对象。
# 返回值 :
# 返回一个 Matplotlib 轴对象,其中包含绘制的直方图。
# sns.histplot 提供了灵活的方式来探索和展示数据的分布情况,特别适合于快速理解数据特征和分布形状。
# 在第三个子图上绘制 x 和 y 坐标的直方图。
sn.histplot(x, x='x', y='y', ax=ax[2], bins=50, pmax=0.9)
# 在第四个子图上绘制宽度和高度的直方图。
sn.histplot(x, x='width', y='height', ax=ax[3], bins=50, pmax=0.9)
# rectangles
# 将边界框的中心坐标设置为 0.5。
labels[:, 1:3] = 0.5 # center
# 将边界框从中心点和宽高格式转换为左上角和右下角坐标格式,并缩放到 2000 像素。
labels[:, 1:] = xywh2xyxy(labels[:, 1:]) * 2000
# Image.fromarray(arr, mode=None)
# Image.fromarray 是 Python Imaging Library (PIL) 中的一个函数,它用于将一个数组(通常是 NumPy 数组)转换成 PIL 图像对象。这个函数非常有用,因为它允许你在图像处理和分析中轻松地在数组和图像对象之间转换。
# 参数说明 :
# arr :一个数组对象,通常是 NumPy 数组。这个数组包含了图像的像素数据。
# mode :(可选)一个字符串,指定图像模式。如果未指定, fromarray 将根据数组的形状和数据类型自动选择一个模式。常见的模式包括 "L"(灰度图),"RGB"(真彩色图像),"RGBA"(带有透明度通道的真彩色图像)等。
# 返回值 :
# 返回一个 PIL 图像对象,你可以使用 PIL 提供的方法对这个对象进行操作,比如旋转、缩放、裁剪等。
# 创建一个 2000x2000 像素的白色背景图像。
img = Image.fromarray(np.ones((2000, 2000, 3), dtype=np.uint8) * 255)
# 遍历前 1000 个边界框。
for cls, *box in labels[:1000]:
# 在图像上绘制边界框。
ImageDraw.Draw(img).rectangle(box, width=1, outline=colors(cls)) # plot
# 在第二个子图上显示绘制了边界框的图像。
ax[1].imshow(img)
# 关闭第二个子图的坐标轴。
ax[1].axis('off')
# 遍历所有子图。
for a in [0, 1, 2, 3]:
# 遍历所有边。
for s in ['top', 'right', 'left', 'bottom']:
# 关闭子图的边框。
ax[a].spines[s].set_visible(False)
# 保存绘制的标签图为 JPG 文件。
plt.savefig(save_dir / 'labels.jpg', dpi=200)
# 设置 Matplotlib 使用 'Agg' 后端,以便保存图像到文件。
matplotlib.use('Agg')
# 关闭当前的绘图窗口。
plt.close()
# plot_labels 函数用于可视化数据集中的标签分布情况,包括类别分布、边界框尺寸和位置的直方图,以及边界框的图像表示。这个函数提供了一个全面的视图,帮助理解数据集中标签的特征和分布。
15.def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')):
# 这段代码定义了一个名为 imshow_cls 的函数,用于显示分类图像网格,并可选地显示标签和预测结果。
# 定义了一个名为 imshow_cls 的函数,接受以下参数 :
# 1.im :要显示的图像批次。
# 2.labels :真实标签(可选)。
# 3.pred :预测标签(可选)。
# 4.names :类别名称列表(可选)。
# 5.nmax :要显示的最大图像数量,默认为25。
# 6.verbose :是否打印额外信息,默认为False。
# 7.f :保存图像的文件路径,默认为 'images.jpg'。
def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')):
# Show classification image grid with labels (optional) and predictions (optional) 显示带有标签(可选)和预测(可选)的分类图像网格。
# 从 utils.augmentations 模块导入 denormalize 函数,用于将图像数据反归一化。
from utils.augmentations import denormalize
# 如果未提供 names ,则使用默认的类别名称列表。
names = names or [f'class{i}' for i in range(1000)]
# 将反归一化的 图像批次 分割成单独的图像块。
# def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): -> 用于对已经标准化(归一化)的图像数据进行反标准化(反归一化)处理。反标准化是将数据从均值为0、标准差为1的分布恢复到原始分布的过程。回反标准化后的图像数据 x 。 -> return x
blocks = torch.chunk(denormalize(im.clone()).cpu().float(), len(im),
dim=0) # select batch index 0, block by channels
# 确定要显示的图像数量,不超过 nmax 。
n = min(len(blocks), nmax) # number of plots
# 确定每行显示的图像数量,最多8个,且为 n 的平方根的整数部分。
m = min(8, round(n ** 0.5)) # 8 x 8 default
# 创建子图布局,行数为 n / m 的上限整数,列数为 m 。
fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols
# 如果 m 大于1,则将子图数组展平,否则将其转换为单元素列表。
ax = ax.ravel() if m > 1 else [ax]
# plt.subplots_adjust(wspace=0.05, hspace=0.05)
# 遍历每个要显示的图像。
for i in range(n):
# 在子图上显示图像,调整维度顺序并裁剪值以确保在 [0, 1] 范围内。
ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0))
# 关闭子图的坐标轴。
ax[i].axis('off')
# 如果提供了标签,则执行以下操作。
if labels is not None:
# 构造显示的标题,包括真实标签和(如果提供)预测标签。
s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '')
# 设置子图的标题。
ax[i].set_title(s, fontsize=8, verticalalignment='top')
# 将绘制的图像保存为文件。
plt.savefig(f, dpi=300, bbox_inches='tight')
# 关闭绘图窗口。
plt.close()
# 如果 verbose 为 True ,则执行以下操作。
if verbose:
# 记录保存图像的信息。
LOGGER.info(f"Saving {f}")
# 如果提供了真实标签,则记录真实标签。
if labels is not None:
LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax]))
# 如果提供了预测标签,则记录预测标签。
if pred is not None:
LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax]))
# 返回保存图像的文件路径。
return f
# imshow_cls 函数用于可视化分类任务中的图像,并可选地显示真实标签和预测标签。它通过创建子图布局来显示图像,并在每个子图上显示相应的标签信息。这个函数对于分析模型的预测结果非常有用,可以帮助快速识别模型性能。
16.def plot_evolve(evolve_csv='path/to/evolve.csv'):
# 这段代码定义了一个名为 plot_evolve 的函数,用于绘制从 evolve.csv 文件中得到的超参数进化结果。这个函数使用了 Matplotlib 和 Pandas 库来处理数据和绘图。
# 定义了一个名为 plot_evolve 的函数,接受一个参数。
# 1.evolve_csv :这是 evolve.csv 文件的路径,默认为 'path/to/evolve.csv' 。
def plot_evolve(evolve_csv='path/to/evolve.csv'): # from utils.plots import *; plot_evolve()
# Plot evolve.csv hyp evolution results 绘制 evolve.csv hyp 进化结果。
# 将 evolve_csv 参数转换为 Path 对象,以便使用路径操作。
evolve_csv = Path(evolve_csv)
# pandas.read_csv(file_name, **kwargs)
# pd.read_csv() 是 Python 中 pandas 库的一个函数,用于读取 CSV(逗号分隔值)文件,并将其转换为 pandas 的 DataFrame 对象。DataFrame 是 pandas 中用于存储和操作结构化数据的主要数据结构。
# 参数 :
# file_name :文件路径或文件对象,指向要读取的 CSV 文件。
# 可选参数(kwargs) :
# header :指定作为列名的第一行,默认为0(第一行)。
# sep :指定字段的分隔符,默认为逗号( , )。
# delimiter :与 sep 类似,但在某些情况下可以提供更多灵活性。
# names :列名列表,用于没有列名的 CSV 文件。
# index_col :指定作为行索引的列。
# usecols :需要读取的列。
# squeeze :如果为 True ,并且文件只有一列,则返回 Series 而不是 DataFrame。
# dtype :指定列的数据类型。
# skiprows :跳过文件开始的行数或需要跳过的行的列表。
# nrows :需要读取的行数。
# na_values :将这些值视为缺失值。
# keep_default_na :是否保留默认的缺失值(例如 'nan' 、 'null' 等)。
# skipfooter :从文件末尾跳过的行数。
# verbose :是否打印详细的进度信息。
# parse_dates :是否解析日期。
# infer_datetime_format :是否推断日期时间格式。
# keep_date_col :是否在解析日期后保留原始列。
# dayfirst :日期格式中天是否在前。
# cache_dates :是否缓存日期解析。
# iterator :返回一个迭代器而不是 DataFrame。
# chunksize :返回一个迭代器时,每次迭代的行数。
# compression :文件压缩类型。
# quotechar :用于引用字段的字符。
# escapechar :用于转义的字符。
# encoding :文件编码。
# decimal :小数点字符。
# lineterminator :行终止符。
# quotechar :引用字符。
# 返回值 :
# 返回一个 DataFrame 对象,其中包含了 CSV 文件的内容。
# 使用场景 :
# 读取 CSV 文件并进行数据分析。
# 数据清洗和预处理。
# 数据科学和机器学习中的数据处理。
# 使用 Pandas 的 read_csv 函数读取 evolve.csv 文件中的数据。
data = pd.read_csv(evolve_csv)
# 提取数据框的列名,并去除可能的空白字符,存储在 keys 列表中。
keys = [x.strip() for x in data.columns]
# 将数据框转换为 NumPy 数组。
# 使用 Pandas 库处理数据时, data.values 是一种常见的属性访问方式,用于从 Pandas DataFrame 中提取其数值数据。
# 这行代码用于将 Pandas DataFrame 中的数值数据转换为 NumPy 数组,以便进行进一步的数值计算或数据分析。这个操作在数据科学和机器学习中非常常见,因为它允许开发者利用 NumPy 的强大功能来处理数据。
x = data.values
# 调用 fitness 函数计算每个超参数组合的适应度值。
# def fitness(x): -> 计算模型的适应度,作为一系列度量指标的加权组合。返回值。函数返回一个包含每个模型适应度得分的数组。 -> return (x[:, :4] * w).sum(1)
f = fitness(x)
# 找到适应度值最大的索引。
j = np.argmax(f) # max fitness index
# 创建一个新的 Matplotlib 图形,设置图形大小为 10x12 英寸,并启用紧凑布局。
plt.figure(figsize=(10, 12), tight_layout=True)
# matplotlib.rc(params, **kwargs)
# matplotlib.rc() 函数是 Matplotlib 库中的一个功能,用于设置或检索 Matplotlib 的配置参数。这个函数允许用户自定义 Matplotlib 图形的各种属性,包括图形大小、字体、颜色、线条样式等。
# 参数说明 :
# params :一个字典或字符串,指定要设置的参数。如果是一个字典,它包含参数名和值的映射;如果是一个字符串,它指定一个参数文件或一个参数组。
# **kwargs :关键字参数,用于指定要设置的参数和它们的值。
# 返回值 : matplotlib.rc() 没有返回值,它直接修改 Matplotlib 的配置。
# matplotlib.rc() 函数是一个强大的工具,用于自定义 Matplotlib 图形的外观和行为。通过这个函数,用户可以根据需要调整图形的各种参数,以满足特定的可视化需求。
# 设置图形中的字体大小为 8。
matplotlib.rc('font', **{'size': 8})
# 打印最佳结果所在的行号和文件名。
print(f'Best results from row {j} of {evolve_csv}:') # {evolve_csv} 第 {j} 行的最佳结果:
# 遍历 keys 列表中从第 8 个元素开始的部分,这通常表示超参数的名称。
for i, k in enumerate(keys[7:]):
# 从数据中提取第 i 个超参数的所有值。
v = x[:, 7 + i]
# 获取最佳超参数组合中第 i 个超参数的值。
mu = v[j] # best single result
# 创建子图,6 行 5 列的布局中的第 i+1 个位置。
plt.subplot(6, 5, i + 1)
# 绘制散点图,其中 v 是超参数值, f 是适应度值,使用 hist2d 函数为每个点着色。
plt.scatter(v, f, c=hist2d(v, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
# 在散点图上标记最佳超参数值的位置。
plt.plot(mu, f.max(), 'k+', markersize=15)
# 设置子图的标题,显示超参数名称和最佳值。
plt.title(f'{k} = {mu:.3g}', fontdict={'size': 9}) # limit to 40 characters
# 如果 i 不是 5 的倍数,则执行以下操作。
if i % 5 != 0:
# 隐藏 y 轴的刻度。
plt.yticks([])
# 打印超参数名称和最佳值。
print(f'{k:>15}: {mu:.3g}')
# 构造保存图像的文件路径,将 .csv 后缀替换为 .png 。
f = evolve_csv.with_suffix('.png') # filename
# 将图形保存为 PNG 文件,设置分辨率为 200 dpi。
plt.savefig(f, dpi=200)
# 关闭绘图窗口。
plt.close()
# 打印保存的文件路径。
print(f'Saved {f}')
# plot_evolve 函数用于可视化超参数进化的结果。它读取 evolve.csv 文件中的数据,计算适应度值,并为每个超参数绘制散点图,显示其与适应度值的关系。这个函数有助于理解不同超参数值对模型性能的影响,并识别最佳超参数组合。
17.def plot_results(file='path/to/results.csv', dir=''):
# 这段代码定义了一个名为 plot_results 的函数,用于绘制训练结果,通常来自一个或多个 results.csv 文件。
# 定义了一个名为 plot_results 的函数,接受两个参数。
# 1.file :要绘制的 results.csv 文件的路径,默认为 'path/to/results.csv' 。
# 2.dir :包含结果文件的目录路径,默认为空字符串。
def plot_results(file='path/to/results.csv', dir=''):
# Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv') 绘制训练 results.csv。用法:来自 utils.plots 导入 *;plot_results('path/to/results.csv') 。
# 如果提供了 file 参数,则使用文件的父目录作为保存目录;否则,使用 dir 参数作为保存目录。
save_dir = Path(file).parent if file else Path(dir)
# 创建一个图形和 10 个子图(2x5布局),设置图形大小为 12x6 英寸,并启用紧凑布局。
fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True)
# 将子图数组展平,使其成为一个一维数组,方便遍历。
ax = ax.ravel()
# 获取保存目录中所有以 results 开头的 .csv 文件。
files = list(save_dir.glob('results*.csv'))
# 确保至少存在一个 results.csv 文件,否则抛出异常。
assert len(files), f'No results.csv files found in {save_dir.resolve()}, nothing to plot.' # 在 {save_dir.resolve()} 中未找到 results.csv 文件,没有可绘制的内容。
# 遍历所有找到的 results.csv 文件。
for f in files:
# 尝试执行以下操作,以便在发生错误时捕获异常。
try:
# 使用 Pandas 读取当前 results.csv 文件中的数据。
data = pd.read_csv(f)
# 提取数据框的列名,并去除可能的空白字符,存储在列表 s 中。
s = [x.strip() for x in data.columns]
# 提取数据框的第一列作为 x 轴数据。
x = data.values[:, 0]
# 遍历列索引列表,这些索引对应于要绘制的数据列。
for i, j in enumerate([1, 2, 3, 4, 5, 8, 9, 10, 6, 7]):
# 提取对应列的数据,并转换为浮点数。
y = data.values[:, j].astype('float')
# y[y == 0] = np.nan # don't show zero values
# 在对应的子图上绘制数据,使用点标记,设置线宽和标记大小。
ax[i].plot(x, y, marker='.', label=f.stem, linewidth=2, markersize=8)
# 设置子图的标题,使用列名。
ax[i].set_title(s[j], fontsize=12)
# if j in [8, 9, 10]: # share train and val loss y axes
# ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
# 如果发生异常,捕获并记录错误信息。
except Exception as e:
# 记录绘制过程中的错误信息。
LOGGER.info(f'Warning: Plotting error for {f}: {e}') # 警告:{f} 的绘图错误:{e} 。
# 在第二个子图上显示图例。
ax[1].legend()
# 将绘制的图形保存为 PNG 文件,设置分辨率为 200 dpi。
fig.savefig(save_dir / 'results.png', dpi=200)
# 关闭绘图窗口。
plt.close()
# plot_results 函数用于从 results.csv 文件中读取训练结果数据,并在子图上绘制这些数据。它提供了一个可视化训练过程的工具,可以帮助分析模型性能的变化。这个函数对于理解模型训练过程中的各种指标(如损失、精确度等)的变化非常有用。
18.def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
# 这段代码定义了一个名为 profile_idetection 的函数,用于绘制 iDetection 项目的每张图片日志数据。
# 定义了一个名为 profile_idetection 的函数,接受四个参数。
# 1.start :开始绘制的索引,默认为0。
# 2.stop :停止绘制的索引,默认为0(即绘制到最后)。
# 3.labels :标签列表,用于标记每张图片,默认为空。
# 4.save_dir :保存图像的目录,默认为空字符串。
def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
# Plot iDetection '*.txt' per-image logs. from utils.plots import *; profile_idetection() 绘制 iDetection '*.txt' 每幅图像日志。来自 utils.plots 导入 *;profile_idetection() 。
# 创建一个图形和8个子图(2行4列),设置图形大小为12x6英寸,并启用紧凑布局。 [1] 表示获取第二行的子图数组, ravel() 将子图数组展平。
ax = plt.subplots(2, 4, figsize=(12, 6), tight_layout=True)[1].ravel()
# 定义一个列表 s ,包含每个子图的标题。
s = ['Images', 'Free Storage (GB)', 'RAM Usage (GB)', 'Battery', 'dt_raw (ms)', 'dt_smooth (ms)', 'real-world FPS']
# 获取保存目录中所有以 frames 开头的 .txt 文件。
files = list(Path(save_dir).glob('frames*.txt'))
# 遍历所有找到的 .txt 文件。
for fi, f in enumerate(files):
# 尝试执行以下操作,以便在发生错误时捕获异常。
try:
# 读取当前文件中的数据,转置并裁剪掉第一行和最后一行。
results = np.loadtxt(f, ndmin=2).T[:, 90:-30] # clip first and last rows
# 获取数据的列数。
n = results.shape[1] # number of rows
# 根据 start 和 stop 参数生成 x 轴数据。
x = np.arange(start, min(stop, n) if stop else n)
# 裁剪数据,只保留 x 轴数据对应的列。
results = results[:, x]
# 将时间数据设置为从0开始。
t = (results[0] - results[0].min()) # set t0=0s
# 更新时间数据。
results[0] = x
# 遍历每个子图。
for i, a in enumerate(ax):
# 如果子图索引小于结果数据的长度,则执行以下操作。
if i < len(results):
# 如果提供了标签,则使用标签;否则,使用文件名作为标签。
label = labels[fi] if len(labels) else f.stem.replace('frames_', '')
# 在子图上绘制数据。
a.plot(t, results[i], marker='.', label=label, linewidth=1, markersize=5)
# 设置子图的标题。
a.set_title(s[i])
# 设置子图的 x 轴标签。
a.set_xlabel('time (s)')
# if fi == len(files) - 1:
# a.set_ylim(bottom=0)
# 遍历子图的顶部和右侧边框。
for side in ['top', 'right']:
# 关闭子图的顶部和右侧边框。
a.spines[side].set_visible(False)
# 如果子图索引大于结果数据的长度,则移除子图。
else:
# 移除多余的子图。
a.remove()
# 如果发生异常,捕获并打印错误信息。
except Exception as e:
# 打印错误信息。
print(f'Warning: Plotting error for {f}; {e}') # 警告:{f};{e} 的绘图错误。
# 在第二个子图上显示图例。
ax[1].legend()
# 将绘制的图形保存为 PNG 文件,设置分辨率为 200 dpi。
plt.savefig(Path(save_dir) / 'idetection_profile.png', dpi=200)
# 关闭绘图窗口。
plt.close()
# profile_idetection 函数用于从 iDetection 项目的日志文件中读取数据,并在子图上绘制这些数据。它提供了一个可视化工具,可以帮助分析检测过程中的各种指标(如存储空间、内存使用、电池电量等)随时间的变化。这个函数对于理解检测过程的性能和资源消耗非常有用。
19.def save_one_box(xyxy, im, file=Path('im.jpg'), gain=1.02, pad=10, square=False, BGR=False, save=True):
# 这段代码是定义了 save_one_box 函数,用于保存图像裁剪。它接收多个参数,包括边界框坐标、图像、文件路径、增益、填充、是否为正方形、是否为BGR颜色空间以及是否保存图像。
# 定义了一个名为 save_one_box 的函数,参数包括。
# 1.xyxy :边界框坐标,格式为 (x1, y1, x2, y2) 。
# 2.im :要裁剪的图像。
# 3.file :保存裁剪图像的文件路径,默认为 Path('im.jpg') 。
# 4.gain :裁剪尺寸的增益,默认为 1.02 。
# 5.pad :裁剪区域的填充像素,默认为 10 。
# 6.square :是否将裁剪区域调整为正方形,默认为 False 。
# 7.BGR :图像是否为BGR颜色空间,默认为 False 。
# 8.save :是否保存裁剪后的图像,默认为 True 。
def save_one_box(xyxy, im, file=Path('im.jpg'), gain=1.02, pad=10, square=False, BGR=False, save=True):
# Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop
# 将 xyxy 转换为PyTorch张量,并重塑为 (-1, 4) 的形状。
xyxy = torch.tensor(xyxy).view(-1, 4)
# 将边界框坐标从 xyxy 格式转换为 xywh 格式(中心点坐标和宽高)。
b = xyxy2xywh(xyxy) # boxes
# 如果 square 为 True ,则将边界框调整为正方形。
if square:
b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # attempt rectangle to square
# 对边界框的宽度和高度应用增益和填充。
b[:, 2:] = b[:, 2:] * gain + pad # box wh * gain + pad
# 将边界框坐标从 xywh 格式转换回 xyxy 格式,并转换为整数。
xyxy = xywh2xyxy(b).long()
# 根据图像尺寸裁剪边界框,防止超出图像边界。
clip_boxes(xyxy, im.shape)
# 根据边界框坐标裁剪图像。
crop = im[int(xyxy[0, 1]):int(xyxy[0, 3]), int(xyxy[0, 0]):int(xyxy[0, 2]), ::(1 if BGR else -1)]
# 如果 save 为 True ,则保存裁剪后的图像。
if save:
# 创建文件的父目录。
file.parent.mkdir(parents=True, exist_ok=True) # make directory
# 生成一个唯一的文件路径,并确保文件扩展名为 .jpg 。
# def increment_path(path, exist_ok=False, sep='', mkdir=False):
# -> 为文件或目录生成一个新的路径名,如果原始路径已经存在,则通过在路径后面添加一个数字(默认从2开始递增)来创建一个新的路径。函数返回最终的路径 path 。
# -> return path
f = str(increment_path(file).with_suffix('.jpg'))
# cv2.imwrite(f, crop) # save BGR, https://github.com/ultralytics/yolov5/issues/7007 chroma subsampling issue
# 使用Pillow库保存裁剪后的图像,设置质量为95,并禁用色度子采样(解决色度子采样问题)。
Image.fromarray(crop[..., ::-1]).save(f, quality=95, subsampling=0) # save RGB
# 返回裁剪后的图像。
return crop
# 这个函数的主要作用是裁剪图像中的特定区域,并可以选择保存裁剪后的图像。它还处理了颜色空间转换和色度子采样问题,以提高裁剪图像的质量。