当前位置: 首页 > news >正文

从代码学习深度学习 - 单发多框检测(SSD)PyTorch版

文章目录

  • 前言
  • 工具函数
    • 数据处理工具 (`utils_for_data.py`)
    • 训练工具 (`utils_for_train.py`)
    • 检测相关工具 (`utils_for_detection.py`)
    • 可视化工具 (`utils_for_huitu.py`)
  • 模型
    • 类别预测层
    • 边界框预测层
    • 连接多尺度预测
    • 高和宽减半块
    • 基础网络块
    • 完整的模型
  • 训练模型
    • 读取数据集和初始化
    • 定义损失函数和评价函数
    • 训练模型
  • 预测目标
    • 图像预处理
    • 执行预测和后处理
    • 可视化结果
  • 总结


前言

大家好!欢迎来到“从代码学习深度学习”系列博客。目标检测是计算机视觉领域的核心任务之一,旨在识别图像或视频中特定类别的对象实例,并确定它们的位置和范围。近年来,深度学习技术极大地推动了目标检测的发展,涌现出许多优秀的算法,如 R-CNN 系列、YOLO 系列以及我们今天要重点介绍的单发多框检测(Single Shot MultiBox Detector, SSD)。

SSD 是一种流行的单阶段目标检测器,以其在速度和精度之间的良好平衡而闻名。与两阶段检测器(如 Faster R-CNN)先生成区域提议再进行分类和回归不同,SSD 直接在不同尺度的特征图上预测边界框和类别,从而实现了更快的检测速度。

本篇博客旨在通过一个具体的 PyTorch 实现(基于香蕉检测数据集),带领大家深入理解 SSD 的核心原理和代码实现细节。我们将逐步剖析模型结构、损失函数、训练过程以及预测可视化等关键环节,真正做到“从代码中学习”。

完整代码:下载链接

在深入 SSD 模型之前,我们先引入一些在整个项目中会用到的工具函数,它们主要负责数据处理、模型训练辅助以及结果可视化。

工具函数

在实现和训练 SSD 模型以及可视化结果的过程中,我们会用到一些辅助函数。这些函数分散在不同的工具文件中。

数据处理工具 (utils_for_data.py)

这部分代码负责读取和加载香蕉检测数据集。read_data_bananas 函数读取图像和对应的 CSV 标签文件,并将它们转换成 PyTorch 张量。BananasDataset 类继承了 torch.utils.data.Dataset,方便我们构建数据加载器。load_data_bananas 函数则利用 BananasDataset 创建了训练和验证数据的数据加载器(DataLoader)。

# --- START OF FILE utils_for_data.py ---import os
import pandas as pd
import torch
import torchvisiondef read_data_bananas(is_train=True):"""读取香蕉检测数据集中的图像和标签参数:is_train (bool): 是否读取训练集数据,True表示读取训练集,False表示读取验证集返回:tuple: (images, targets)- images: 图像列表,每个元素是一个形状为[C, H, W]的张量- targets: 标注信息张量,形状为[N, 1, 5],每行包含[类别, 左上角x, 左上角y, 右下角x, 右下角y]"""# 设置数据目录路径data_dir = 'banana-detection'# 根据is_train确定使用训练集还是验证集路径subset_name = 'bananas_train' if is_train else 'bananas_val'# 构建标签CSV文件的完整路径# csv_fname: 字符串,表示CSV文件的完整路径csv_fname = os.path.join(data_dir, subset_name, 'label.csv')# 读取CSV文件到pandas DataFrame# csv_data: DataFrame,包含图像名称和对应的标注信息csv_data = pd.read_csv(csv_fname)# 将img_name列设置为索引,便于后续访问# csv_data: DataFrame,索引为图像名称,列为标注信息csv_data = csv_data.set_index('img_name')# 初始化存储图像和标注的列表# images: 列表,用于存储读取的图像张量# targets: 列表,用于存储对应的标注信息images, targets = [], []# 遍历DataFrame中的每一行,读取图像和对应的标注信息for img_name, target in csv_data.iterrows():# 读取图像并添加到images列表中# img_name: 字符串,图像文件名# 读取的图像: 张量,形状为[C, H, W],C是通道数,H是高度,W是宽度images.append(torchvision.io.read_image(os.path.join(data_dir, subset_name, 'images', f'{img_name}')))# 添加标注信息到targets列表中# target: Series,包含类别和边界框坐标信息# list(target): 列表,形状为[5],包含[类别, 左上角x, 左上角y, 右下角x, 右下角y]targets.append(list(target))# 将targets列表转换为张量,并添加一个维度,然后将值归一化到0-1范围# torch.tensor(targets): 张量,形状为[N, 5],N是样本数量# torch.tensor(targets).unsqueeze(1): 张量,形状为[N, 1, 5]# 最终返回的targets: 张量,形状为[N, 1, 5],值范围在0-1之间targets_tensor = torch.tensor(targets).unsqueeze(1) / 256return images, targets_tensorclass BananasDataset(torch.utils.data.Dataset):"""一个用于加载香蕉检测数据集的自定义数据集类继承自torch.utils.data.Dataset基类,实现了必要的__init__、__getitem__和__len__方法用于提供数据加载器(DataLoader)访问数据集的接口"""def __init__(self, is_train):"""初始化香蕉检测数据集参数:is_train (bool): 是否加载训练集数据,True表示加载训练集,False表示加载验证集属性:self.features: 列表,包含所有图像张量,每个张量形状为[C, H, W]self.labels: 张量,形状为[N, 1, 5],其中N是样本数量,1是类别数量每个样本包含[类别, 左上角x, 左上角y, 右下角x, 右下角y]"""# 调用read_data_bananas函数读取数据集# self.features: 列表,包含N个形状为[C, H, W]的图像张量# self.labels: 张量,形状为[N, 1, 5]self.features, self.labels = read_data_bananas(is_train)# 打印读取的数据集信息dataset_type = '训练样本' if is_train else '验证样本'print(f'读取了 {len(self.features)}{dataset_type}')def __getitem__(self, idx):"""获取指定索引的样本参数:idx (int): 样本索引返回:tuple: (feature, label)- feature: 张量,形状为[C, H, W],图像数据,已转换为float类型- label: 张量,形状为[1, 5],对应的标注信息"""# 返回索引为idx的特征和标签对# self.features[idx]: 张量,形状为[C, H, W]# self.features[idx].float(): 将图像张量转换为float类型,形状不变,仍为[C, H, W]# self.labels[idx]: 张量,形状为[1, 5],包含一个目标的类别和边界框信息return (self.features[idx].float(), self.labels[idx])def __len__(self):"""获取数据集中样本的数量返回:int: 数据集中的样本数量"""# 返回数据集中的样本数量# len(self.features): int,表示数据集中图像的总数return len(self.features)def load_data_bananas(batch_size):"""加载香蕉检测数据集,并创建数据加载器参数:batch_size (int): 批量大小,指定每次加载的样本数量返回:tuple: (train_iter, val_iter)- train_iter: 训练数据加载器,每次返回batch_size个训练样本每个批次包含:- 特征张量,形状为[batch_size, C, H, W]- 标签张量,形状为[batch_size, 1, 5]- val_iter: 验证数据加载器,每次返回batch_size个验证样本批次格式与train_iter相同"""# 创建训练集数据加载器# BananasDataset(is_train=True): 实例化训练集数据集对象# batch_size: 每个批次的样本数量# shuffle=True: 打乱数据顺序,增强模型的泛化能力# train_iter的每个批次包含:# - 特征张量,形状为[batch_size, C, H, W],C是通道数,H是高度,W是宽度# - 标签张量,形状为[batch_size, 1, 5],每行包含[类别, 左上角x, 左上角y, 右下角x, 右下角y]train_iter = torch.utils.data.DataLoader(BananasDataset(is_train=True),batch_size=batch_size,shuffle=True)# 创建验证集数据加载器# BananasDataset(is_train=False): 实例化验证集数据集对象# batch_size: 每个批次的样本数量# shuffle默认为False: 不打乱验证数据的顺序,保持一致性# val_iter的每个批次包含:# - 特征张量,形状为[batch_size, C, H, W]# - 标签张量,形状为[batch_size, 1, 5]val_iter = torch.utils.data.DataLoader(BananasDataset(is_train=False),batch_size=batch_size)return train_iter, val_iter
# --- END OF FILE utils_for_data.py ---

训练工具 (utils_for_train.py)

这部分包含通用的训练辅助类。Timer 类用于记录和计算代码块的执行时间。Accumulator 类则方便我们在训练过程中累加损失、准确率等多个指标。try_gpu 函数尝试获取可用的 GPU 设备,否则回退到 CPU。

# --- START OF FILE utils_for_train.py ---import torch
import math   # 导入math包,用于计算指数
from torch import nn
import time
import numpy as np # 导入numpy 用于cumsum计算class Timer:"""记录多次运行时间"""def __init__(self):"""Defined in :numref:`subsec_linear_model`"""self.times = []self.start()def start(self):"""启动计时器"""self.tik = time.time()def stop(self):"""停止计时器并将时间记录在列表中"""self.times.append(time.time() - self.tik)return self.times[-1]def avg(self):"""返回平均时间"""return sum(self.times) / len(self.times)def sum(self):"""返回时间总和"""return sum(self.times)def cumsum(self):"""返回累计时间"""return np.array(self.times).cumsum().tolist()class Accumulator:"""在 n 个变量上累加"""def __init__(self, n):"""初始化 Accumulator 类输入:n: 需要累加的变量数量  # 输入参数:变量数量输出:无返回值  # 方法无显式返回值"""self.data = [0.0] * n  # 初始化一个长度为 n 的浮点数列表,初始值为 0.0def add(self, *args):"""向累加器中添加多个值输入:*args: 可变数量的数值,用于累加  # 输入参数:可变参数,表示要累加的值输出:无返回值  # 方法无显式返回值"""self.data = [a + float(b) for a, b in zip(self.data, args)]  # 将输入值累加到对应位置的数据上def reset(self):"""重置累加器中的所有值为 0输入:无  # 方法无输入参数输出:无返回值  # 方法无显式返回值"""self.data = [0.0] * len(self.data)  # 重置数据列表,所有值设为 0.0def __getitem__(self, idx):"""获取指定索引处的值输入:idx: 索引值  # 输入参数:要访问的数据索引输出:float: 指定索引处的值  # 返回指定位置的累加值"""return self.data[idx]  # 返回指定索引处的数据值def try_gpu(i=0):"""如果存在,则返回gpu(i),否则返回cpu()Args:i (int, optional): GPU设备的编号,默认为0,表示尝试使用第0号GPUReturns:torch.device: 返回可用的设备对象,如果指定编号的GPU可用则返回GPU,否则返回CPU"""# 检查系统中可用的GPU数量是否大于等于i+1if torch.cuda.device_count() >= i + 1:# 如果条件满足,返回指定编号i的GPU设备return torch.device(f'cuda:{i}')# 如果没有足够的GPU设备,返回CPU设备return torch.device('cpu')# --- END OF FILE utils_for_train.py ---

检测相关工具 (utils_for_detection.py)

这是 SSD 实现的核心工具集。包含了以下关键功能:

  • 边界框表示转换: box_corner_to_centerbox_center_to_corner 用于在 (左上角, 右下角) 和 (中心点, 宽高) 两种坐标表示法之间转换。
  • 锚框生成: multibox_prior 根据输入的特征图、尺寸比例 (sizes) 和宽高比 (ratios) 生成大量的锚框。
  • IoU 计算: box_iou 计算两组边界框之间的交并比 (Intersection over Union),这是目标检测中的基本度量。
  • 锚框分配: assign_anchor_to_bbox 将真实边界框 (ground truth) 分配给最匹配的锚框。
  • 偏移量计算: offset_boxes 计算预测边界框相对于锚框的偏移量(中心点坐标和宽高),这是回归任务的目标。offset_inverse 则根据锚框和预测的偏移量反算出预测的边界框坐标。
  • 目标生成: multibox_target 是关键函数,它整合了锚框分配和偏移量计算,为每个锚框生成对应的类别标签和边界框回归目标。
  • 非极大值抑制 (NMS): nms 用于在预测阶段去除高度重叠的冗余检测框,保留置信度最高的框。
  • 多框检测: multibox_detection 结合类别概率预测、边界框偏移量预测、锚框以及 NMS,生成最终的检测结果。
  • 可视化辅助: bbox_to_rect 将边界框转换为 Matplotlib 绘图格式,show_bboxes 则用于在图像上绘制边界框和标签。
# --- START OF FILE utils_for_detection.py ---import torch
import matplotlib.pyplot as plt
torch.set_printoptions(2)  # 精简输出精度def box_corner_to_center(boxes):"""将边界框从(左上角,右下角)表示法转换为(中心点,宽度,高度)表示法该函数接收以(x1, y1, x2, y2)格式表示的边界框张量,其中:- (x1, y1):表示边界框左上角的坐标- (x2, y2):表示边界框右下角的坐标然后将其转换为(cx, cy, w, h)格式,其中:- (cx, cy):表示边界框中心点的坐标- w:表示边界框的宽度- h:表示边界框的高度参数:boxes (torch.Tensor): 形状为(N, 4)的张量,包含N个边界框的左上角和右下角坐标返回:torch.Tensor: 形状为(N, 4)的张量,包含N个边界框的中心点坐标、宽度和高度"""# 分别提取所有边界框的左上角和右下角坐标x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]# 计算中心点坐标cx = (x1 + x2) / 2  # 中心点x坐标 = (左边界x + 右边界x) / 2cy = (y1 + y2) / 2  # 中心点y坐标 = (上边界y + 下边界y) / 2# 计算宽度和高度w = x2 - x1  # 宽度 = 右边界x - 左边界xh = y2 - y1  # 高度 = 下边界y - 上边界y# 将计算得到的中心点坐标、宽度和高度堆叠成新的张量boxes = torch.stack((cx, cy, w, h), axis=-1)return boxesdef box_center_to_corner(boxes):"""将边界框从(中心点,宽度,高度)表示法转换为(左上角,右下角)表示法该函数接收以(cx, cy, w, h)格式表示的边界框张量,其中:- (cx, cy):表示边界框中心点的坐标- w:表示边界框的宽度- h:表示边界框的高度然后将其转换为(x1, y1, x2, y2)格式,其中:- (x1, y1):表示边界框左上角的坐标- (x2, y2):表示边界框右下角的坐标参数:boxes (torch.Tensor): 形状为(N, 4)的张量,包含N个边界框的中心点坐标、宽度和高度返回:torch.Tensor: 形状为(N, 4)的张量,包含N个边界框的左上角和右下角坐标"""# 分别提取所有边界框的中心点坐标、宽度和高度cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]# 计算左上角坐标x1 = cx - 0.5 * w  # 左边界x = 中心点x - 宽度/2y1 = cy - 0.5 * h  # 上边界y = 中心点y - 高度/2# 计算右下角坐标x2 = cx + 0.5 * w  # 右边界x = 中心点x + 宽度/2y2 = cy + 0.5 * h  # 下边界y = 中心点y + 高度/2# 将计算得到的左上角和右下角坐标堆叠成新的张量boxes = torch.stack((x1, y1, x2, y2), axis=-1)return boxesdef multibox_prior(data, sizes, ratios):"""生成以每个像素为中心具有不同形状的锚框参数:data:输入图像张量,维度为(批量大小, 通道数, 高度, 宽度)sizes:锚框缩放比列表,元素个数为num_sizes,每个元素∈(0,1]ratios:锚框宽高比列表,元素个数为num_ratios,每个元素>0返回:输出张量,维度为(1, 像素总数*每像素锚框数, 4),表示所有锚框的坐标"""# 获取输入数据的高度和宽度# in_height, in_width: 标量in_height, in_width = data.shape[-2:]# 获取设备信息以及尺寸和比例的数量# device: 字符串; num_sizes, num_ratios: 标量device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)# 计算每个像素点产生的锚框数量 = 尺寸数 + 宽高比数 - 1# boxes_per_pixel: 标量boxes_per_pixel = (num_sizes + num_ratios - 1)# 将尺寸和比例转换为张量# size_tensor: 维度为(num_sizes,)# ratio_tensor: 维度为(num_ratios,)size_tensor = torch.tensor(sizes, device=device)ratio_tensor = torch.tensor(ratios, device=device)# 为了将锚点移动到像素的中心,需要设置偏移量# 因为一个像素的高为1且宽为1,我们选择偏移中心0.5# offset_h, offset_w: 标量offset_h, offset_w = 0.5, 0.5# 计算高度和宽度方向上的步长(归一化)# steps_h, steps_w: 标量steps_h = 1.0 / in_height  # 在y轴上缩放步长steps_w = 1.0 / in_width   # 在x轴上缩放步长# 生成锚框的所有中心点# center_h: 维度为(in_height,)# center_w: 维度为(in_width,)center_h = (torch.arange(in_height, device=device) + offset_h) * steps_hcenter_w = (torch.arange(in_width, device=device) + offset_w) * steps_w# 使用meshgrid生成网格坐标# shift_y, shift_x: 维度均为(in_height, in_width)shift_y, shift_x = torch.meshgrid(center_h, center_w, indexing='ij')# 将坐标展平为一维# shift_y, shift_x: 展平后维度均为(in_height*in_width,)shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)# 生成"boxes_per_pixel"个高和宽,# 之后用于创建锚框的四角坐标(xmin,ymin,xmax,ymax)# 计算锚框宽度:先计算尺寸与第一个比例的组合,再计算第一个尺寸与其余比例的组合# w: 维度为(num_sizes + num_ratios - 1,)w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),sizes[0] * torch.sqrt(ratio_tensor[1:])))\* in_height / in_width  # 处理矩形输入,调整宽度# 计算锚框高度:对应于宽度的计算方式# h: 维度为(num_sizes + num_ratios - 1,)h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),sizes[0] / torch.sqrt(ratio_tensor[1:]</

相关文章:

从代码学习深度学习 - 单发多框检测(SSD)PyTorch版

文章目录 前言工具函数数据处理工具 (`utils_for_data.py`)训练工具 (`utils_for_train.py`)检测相关工具 (`utils_for_detection.py`)可视化工具 (`utils_for_huitu.py`)模型类别预测层边界框预测层连接多尺度预测高和宽减半块基础网络块完整的模型训练模型读取数据集和初始化…...

因子分析——数学原理及R语言代码

这里写自定义目录标题 因子分析参数估计方法主成分法主因子法 因子旋转 代码实现Reference 因子分析 FactorAnalysis的目的是从多个高度相关的观测变量中提取出少数几个LatentFactor&#xff0c;这些因子代表了变量背后的共通结构&#xff0c;从而实现降维并提升可解释性。 假…...

CSS3 过渡与动画

在现代网页设计中&#xff0c;平滑的过渡和生动的动画效果已成为提升用户体验不可或缺的元素。CSS3 为我们提供了强大的 transition 和 animation 属性&#xff0c;让开发者能够轻松实现各种视觉效果。本文将深入探讨这两大功能的特性和应用场景。 一、CSS3 过渡&#xff08;T…...

【JAVA】【重试间隔】多线程中两种常见的重试间隔

一、前言 报&#xff01;&#xff01; 小南啊&#xff0c;今日有个小任务交给你去办。就是程序中有个数据处理&#xff0c;总是会出错&#xff0c;不知道是什么原因&#xff0c;你去处理一下&#xff01; 二、主题 围绕数据处理问题去看&#xff0c;从中发现&#xff0c;是因为…...

在现代Web应用中集成 PDF.js (pdfjs-dist 5.2 ESM): 通过 jsdelivr 实现动态加载与批注功能的思考

PDF 文档在现代 Web 应用中越来越常见&#xff0c;无论是作为文档预览、报告展示还是在线编辑的载体。Mozilla 的 PDF.js 是一个功能强大的 JavaScript 库&#xff0c;它使得在浏览器端渲染和显示 PDF 文件成为可能&#xff0c;无需依赖原生插件。 本文将深入探讨如何在你的项…...

android ViewModel liveData无法监听之多线程下activityViewModels不安全

我们一般的&#xff0c;会遇到liveData无法监听到结果&#xff0c;可能存在主要2种可能&#xff1a; liveData没有正确注册&#xff1b;liveData连续多次设置值&#xff0c;中间的值&#xff0c;会被丢弃&#xff0c;但最后一次是能监听到的。 但是我们容易忽略一种case&…...

【即插即用涨点模块】DSConv动态蛇形卷积:自适应聚焦细长弯曲的局部结构特征,助力分割高效提点【附源码+注释】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...

守护数字家园:个人博客安全防护指南

前言 在之前的文章《WordPress个人博客搭建&#xff08;一&#xff09;》《WordPress个人博客搭建&#xff08;二&#xff09;》《WordPress个人博客搭建&#xff08;三&#xff09;》中&#xff0c;我们已经在非凡云云服务器上&#xff0c;借助1Panel搭建起属于自己的数字庭院…...

课外活动:简单了解原生测试框架Unittest前置后置的逻辑

简单了解原生测试框架Unittest前置后置的逻辑 一、测试框架执行顺序解析 1.1 基础执行流程 import unittestclass A(unittest.TestCase):classmethoddef setUpClass(cls):print(f"【CLASS START】{cls.__name__}")def setUp(self):print(f"【TEST START】{se…...

带你玩转 Flink TumblingWindow:从理论到代码的深度探索

0.前言 在深入探讨 TumblingWindow 之前&#xff0c;我们先来了解一下流处理或流计算中“窗口”的基本概念。在数据流中&#xff0c;源会持续不断地生成数据&#xff0c;因此计算最终值是不可行的。 在大多数用例中&#xff0c;为了获取有意义的信息&#xff0c;最好使用两种方…...

Java线程安全问题深度解析与解决方案

一、线程安全问题的本质 并发编程的核心挑战&#xff1a;当多个线程同时访问共享资源时&#xff0c;由于操作系统的抢占式调度特性&#xff0c;可能导致不可预期的结果。这种因非原子操作和竞态条件引发的数据不一致问题&#xff0c;称为线程安全问题。 二、经典线程安全问题案…...

python实现的音乐播放器

python实现的音乐播放器 音乐播放器,原来写过一个简陋的例子,可见 https://blog.csdn.net/cnds123/article/details/137874107 那个不能拖动播放进度条上的滑块到新的位置播放。下面介绍的可以拖动播放进度条上的滑块到新的位置播放。 简单实用的音乐播放器 这个简单实用的…...

SMT贴片工艺核心优化与生产实践

内容概要 作为现代电子制造的核心环节&#xff0c;SMT贴片工艺的优化直接决定了产品可靠性与生产效率。本文系统性梳理工艺链中的关键控制点&#xff0c;从锡膏印刷精度到回流焊温度曲线&#xff0c;再到AOI检测技术升级&#xff0c;形成覆盖全流程的优化框架。针对行业普遍存…...

趣味编程:爱心

概述&#xff1a;五月十一号就是母亲节了&#xff0c;本篇博客主要是为母亲所写&#xff0c;这是属于程序员的浪漫&#xff0c;这篇博客是对母亲这么多年无微不至爱的情书。 目录 1. 效果展示 2. 源码展示 3. 代码逻辑详解 3.1 头文件与常量定义 3.2 心形曲线参数方程 3.…...

C语言—指针2

1. const 修饰变量 1.1 const修饰变量 变量被const修饰时&#xff0c;变量此时为常变量&#xff0c;本质为常量&#xff0c;语法上不可被修改&#xff0c;但是如果此时需要修改变量值&#xff0c;可以通过指针的方式修改。 虽然此时通过指针的方式确实修改了变量的值&#xff…...

66、微服务保姆教程(九)微服务的高可用性

微服务的高可用性与扩展 服务的高可用性 集群搭建与负载均衡。服务的故障容错与自愈。分布式事务与一致性 分布式事务的挑战与解决方案。使用 RocketMQ 实现分布式事务。微服务的监控与可观测性 metrics 和日志的收集与分析。sentinel 的监控功能。容器化与云原生 将微服务部署…...

主场景 工具栏 植物卡牌的渲染

前置知识&#xff1a;使用easyx图形库 1.IMAGE内存变量存储的是一张位图(图像)&#xff0c;存储了像素数据(颜色&#xff0c;尺寸等) 2.loadimage(&变量名&#xff0c;"加载的文件路径")表示从文件中加载图像到变量中 3. saveimage("文件路径", &变…...

超详细!RxSwift 中的 BehaviorRelay 使用教程(含原理 + 示例 + 实战)

目录 前言 1.什么是 BehaviorRelay 2.基本使用方式 3.BehaviorRelay的常用API 4.BehaviorRelay 和其它类型的对比 5.BehaviorRelay的使用场景 1.绑定UITableView 2.MVVM 场景下使用 BehaviorRelay 6.使用注意事项以及建议 1.注意事项 2.使用建议总结 7.推荐阅读 前…...

【软件测试学习day7】Junit5

Junit 是单元测试框架&#xff0c;本期掌握 Junit5 的基础用法。 1. 注解 首先引入 Junit 依赖&#xff1a; <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.1</version…...

前端实战中的单例模式:以医疗药敏管理为例

目录 一、什么是单例模式&#xff1f;1. 状态共享性 —— 数据唯一&#xff0c;任意访问&#xff0c;任意修改2. 生命周期控制性 —— 自己掌控何时创建、何时销毁 二、实战分析&#xff1a;医疗药敏管理系统中的单例应用三、其他场景示例单例实现&#xff1a;ConfigManager.ts…...

如何在 Logback 日志框架中加入链路 ID

在 Logback 日志框架中加入链路 ID&#xff0c;能有效将同一条链路的日志串联起来&#xff0c;便于追踪和排查问题。 1. 生成和管理链路 ID 要保证在整个请求链路里都能获取到链路 ID&#xff0c;可借助 ThreadLocal 来实现。以下是一个简单的工具类示例&#xff1a; import…...

晶振:智能设备的“心跳”如何支撑5G与航天

在现代科技飞速发展的时代&#xff0c;智能设备已深度融入我们生活的方方面面&#xff0c;而晶振&#xff0c;作为智能设备的“心跳”&#xff0c;正默默发挥着不可替代的关键作用。无论是翱翔太空的神舟飞船&#xff0c;还是人们手中须臾不离的智能手机&#xff0c;亦或是推动…...

【HarmonyOS 5】App Linking 应用间跳转详解

目录 什么是 App Linking 使用场景 工作原理 如何开发 1.开通 App Linking 2.确定域名 3.服务端部署 applinking.json 文件 4.AGC绑定域名 5.项目配置 6.组装聚合链接 7.解析聚合链接中的参数 其他 如何获取应用ID 什么是 App Linking App Linking 是一款创建跨…...

neo4j官方示例

目录 一、准备数据 1.执行查看结果 二、操作 1.find 单个节点 2.同上&#xff0c;已某个属性去查询 3. 指定查询个数 4.条件查询 5.查询某个人出演的电影汇总 6.查询tom出演的电影中&#xff0c;还有其他演员的信息。 7.查询跟电影(Cloud Atlas)有关的演员&#xff0…...

基于vueflow可拖拽元素的示例(基于官网示例的单文件示例)

效果图 代码 <template><div style"width: 100%;height: calc(100vh - 84px)"><VueFlow :nodes"nodes" :edges"edges" drop"onDrop" dragover"onDragOver" dragleave"onDragLeave"><div cl…...

minio单点登录与集成(免密)

需求&#xff1a;系统A里&#xff0c;需要实现与MINIO单点登录集成&#xff0c;也就是说&#xff0c;登录了系统A&#xff0c;在访问MINIO时不需要再输入用户密码就可以直接访问。 具体场景如下&#xff1a; 在系统A的一个页面里&#xff0c;配置一个按钮链接&#xff0c;点击…...

深入理解 Docker 网络原理:构建高效、灵活的容器网络

在现代软件开发中&#xff0c;Docker 已经成为了容器化技术的代名词&#xff0c;广泛应用于开发、测试和生产环境。Docker 使得开发者能够将应用及其依赖打包成一个轻量级的容器&#xff0c;并通过 Docker 容器化技术来实现高效的部署与管理。 然而&#xff0c;在日常使用 Dock…...

Hutool中的Pair类详解

1. Pair类概述 Hutool工具库中的Pair类是一个简单的键值对数据结构&#xff0c;用于存储两个相关联的对象。它类似于Map的Entry&#xff0c;但更加轻量级&#xff0c;适用于需要临时存储两个相关联数据的场景。 2. Pair类的主要特点 简单轻量&#xff1a;不依赖复杂的数据结…...

没有Mac,我是怎么上传IPA到App Store的?

没有Mac&#xff0c;我是怎么上传IPA到App Store的&#xff1f; 最近赶一个小项目上线&#xff0c;写的是一个Flutter做的App。安卓版本一晚上搞定&#xff0c;iOS上架却差点把人整崩。 不是我技术菜&#xff0c;是实在太麻烦了。最关键的&#xff0c;是我这台Windows笔电根本…...

RISC-V hardfault分析工具,RTTHREAD-RVBACKTRACE

RV BACKTRACE 简介 本文主要讲述RV BACKTRACE 的内部主要原理 没有接触过rvbacktrace可以看下面两篇文章&#xff0c;理解一下如何使用RVBACKTRACE RVBacktrace RISC-V极简栈回溯组件&#xff1a;https://club.rt-thread.org/ask/article/64bfe06feb7b3e29.html RVBacktra…...

c语言if else语句格式(非常详细)

在C语言中&#xff0c;if else 语句是一种常用的条件控制结构&#xff0c;用于根据不同条件执行不同的代码块。 if-else 语句的基本格式 if-else 语句的基本格式如下&#xff1a; if (条件) { // 如果条件为真&#xff0c;执行这里的代码 } else { // 如果条件为假&a…...

Logback官方文档翻译章节目录

Logback官方文档翻译章节目录 第一章 Logback简介 第二章 Logback的架构&#xff08;一&#xff09; Logback的架构&#xff08;二&#xff09; Logback的架构&#xff08;三&#xff09; 持续更新中…...

按摩椅的机芯类型和材质

按摩椅的机芯类型和材质是影响其按摩效果、使用寿命以及舒适度的重要因素。下面我将从这两个方面详细为你解析&#xff1a; 一、按摩椅机芯类型 按摩椅的“机芯”相当于它的“心脏”&#xff0c;决定了按摩手法、力度、覆盖范围等关键性能。 常见机芯类型&#xff08;按技术发…...

HarmonyOS-hdc远程网络方式连接设备

hdc工具使用手册 1 hdc简介 hdc&#xff08;OpenHarmony Device Connector&#xff09;是为开发人员提供的用于设备连接调试的命令行工具&#xff0c;pc端开发机使用命令行工具hdc&#xff0c;该工具需支持部署在Windows/Linux/Mac等系统上与OpenHarmony设备&#xff08;或模…...

秋招准备——2.跨时钟相关

格雷码异步FIFO跨时钟域处理 格雷码 一、格雷码规律 相邻性&#xff1a;相邻两个数的格雷码只有一位不同&#xff0c;例如&#xff1a; 0000 → 0001&#xff08;仅最低位变化&#xff09;0001 → 0011&#xff08;仅次低位变化&#xff09;0011 → 0010&#xff08;仅最低位…...

【开源版】likeshop上门家政系统PHP版全开源+uniapp前端

一.系统介绍 likeshop_上门家政系统&#xff0c;PHP版本更新至2.1.1最新版&#xff0c;全开源&#xff0c;适用于上门家政场景&#xff0c;系统拥有用户端、师傅端、无论运营还是二开都是性价比极高的100%开源家政系统。 二.搭建环境-教程 系统环境&#xff1a;CentOS、 运行…...

Memgraph 的安装教程

目录 Memgraph 安装步骤1. 使用 Docker 安装 Memgraph2. 使用 Memgraph Lab3. 使用 Python 客户端连接 Memgraph Memgraph 安装步骤 1. 使用 Docker 安装 Memgraph Memgraph 可以通过 Docker 快速安装和运行。以下是使用 Docker 安装 Memgraph 的步骤&#xff1a; 安装 Docke…...

华为网路设备学习-21 路由过滤(filter-policy)

一、路由过滤&#xff08;filter-policy&#xff09; 1、用于控制路由更新、接收的一个工具 2、只能过滤路由信息&#xff0c;无法过滤LSA 二、路由过滤&#xff08;filter-policy&#xff09;与动态路由协议 1、距离矢量路由协议 RIP动态路由协议 交换的是路由表&#xff0…...

Mac 平台 字体Unicode范围分析器

字体Unicode范围分析器 #include <CoreText/CoreText.h> // CoreText框架头文件&#xff0c;用于字体处理 #include <CoreFoundation/CoreFoundation.h> // CoreFoundation框架头文件 #include <stdio.h> // 标准输入输出 #include…...

Android不能下载Gradle,解决方法Could not install Gradle distribution from.......

外网下载速度太慢导致失败&#xff0c;换成国内镜像&#xff0c;可加速下载&#xff1a; 官网地址&#xff1a;https://services.gradle.org/distributions/ 腾讯云镜像 Gradle下载地址&#xff1a;https://mirrors.cloud.tencent.com/gradle/ 阿里云镜像 Gradle下载地址&…...

树状数组的操作问题--Python

树状数组的操作问题 一、问题引入二、解题步骤1.思维导图2.解题步骤 三、代码实现1.代码2.复杂度分析 四、个人总结 一、问题引入 请编写程序&#xff0c;实现树状数组区间求前缀和、单点修改的操作。 输入格式&#xff1a; 输入首先给出一个正整数 n&#xff08;2≤n<10^…...

FEKO许可限制

随着科技的飞速发展&#xff0c;电磁仿真软件在多个领域发挥着越来越重要的作用。FEKO作为一款业界领先的电磁仿真软件&#xff0c;广泛应用于通信、雷达、航空航天、电子对抗等领域。然而&#xff0c;为了确保软件使用的合规性与高效性&#xff0c;FEKO设定了相应的许可限制。…...

第5章 深度学习和卷积神经网络

深度学习是人工智能的一种实现方法。本章我们将考察作为深度学习的代表的卷积神经网络的数学结构。 5-1小恶魔来讲解卷积神经网络的结构 深度学习是重叠了很多层的隐藏层&#xff08;中间层&#xff09;的神经网络。这样的神经网络使隐藏层具有一定的结构&#xff0c;从而更加…...

window 显示驱动开发-处理内存段(一)

视频内存管理器 (VidMm) 负责管理 GPU 的地址空间。 在此之前&#xff0c;内核模式显示微型端口驱动程序 (KMD) 必须通过使用内存段将 GPU 的地址空间描述为 VidMm。 KMD 创建内存段以概括和虚拟化视频内存资源。 它可以根据硬件支持的存储器类型&#xff08;例如&#xff0c;…...

QT实现曲线图缩放、拖拽以及框选放大

.h文件 protected: void saveAxisRange();void wheelEvent(QWheelEvent *event) override;void mousePressEvent(QMouseEvent *event) override;void mouseMoveEvent(QMouseEvent *event) override;void mouseReleaseEvent(QMouseEvent *event) override;private:QPoint m_…...

龙虎榜——20250508

上证假阴包阳的走势&#xff0c;量能较昨天有萎缩&#xff0c;在前期压力附近~ 深证这两天假阴包阳的走势&#xff0c;60分钟未突破昨天的高点&#xff0c;缺口也未补等待明天的选择~ 2025年5月8日龙虎榜行业方向分析 一、核心行业方向 军工航天&#xff08;政策催化地缘驱动…...

SEMI E40-0200 STANDARD FOR PROCESSING MANAGEMENT(加工管理标准)-(三)完结

10 消息服务详情 10.1 本章定义实现加工管理概念所需的消息服务。这些消息已在第8.1节中初步介绍。 协议无关性&#xff1a;这些服务独立于所使用的消息协议&#xff0c;可映射至SECS-II&#xff08;SEMI E5&#xff09;或其他类似协议。 10.1.1 消息服务定义内容包括&#…...

算法竞赛进阶指南.次小生成树

目录 题目算法标签: K r u s k a l Kruskal Kruskal, M S T MST MST, 倍增优化, l c a lca lca思路代码*警示后人 题目 356. 次小生成树 算法标签: K r u s k a l Kruskal Kruskal, M S T MST MST, 倍增优化, l c a lca lca 思路 因为要求的是严格次小生成树, 假设最…...

ElasticSearch基本概念

为什么要使用ElasticSearch Elasticsearch 主要为系统提供搜索功能&#xff0c; MySQL 这类传统关系型数据库主要为系统提供数据存储功能 Elasticsearch 的优势 &#xff1a; 支持多种数据类型&#xff0c;非结构化&#xff0c;数值&#xff0c;地理信息。简单的 RESTful AP…...

普通IT的股票交易成长史--20250508晚复盘

声明&#xff1a;本文章的内容只是自己学习的总结&#xff0c;不构成投资建议。价格行为理论学习可参考简介中的几位&#xff0c;感谢他们的无私奉献。 送给自己的话&#xff1a; 仓位就是生命&#xff0c;绝对不能满仓&#xff01;&#xff01;&#xff01;&#xff01;&…...