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

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

我们在之前实现YOLOv1的基础上,加入了先验框机制,快速的实现了YOLOv2的网络架构,并且实现了前向推理过程。

经典目标检测YOLO系列(二)YOLOV2的复现(1)总体网络架构及前向推理过程

如前所述,我们使用基于先验框的正样本匹配策略。

1 正样本匹配策略

1.1 基于先验框的正样本匹配策略

  • 由于每个网格只输出一个边界框,因此在YOLOv1中的正样本匹配策略很简单,目标框的中心点落在哪个网格,这个网格(左上角点)就是正样本。
  • 但是,我们现在引入了先验框机制,每个网格会输出5个预测框。那么目标框的中心点所在的每一个网格,我们都需要确定这5个预测框中,哪些是正样本,哪些是负样本。
  • 既然我们已经有了具有边界框尺寸信息的先验框,那么我们可以基于先验框来筛选正样本。

假设一个含有目标框中心的网格上的5个先验框分别为A、B、C、D、E,那么需要计算这5个先验框与目标框O的IoU值,分别为:IoU_A、IoU_B、IoU_C、IoU_D、IoU_E,然后设定一个阈值iou_thresh:

  • 第1种情况:如果IoU_A、IoU_B、IoU_C、IoU_D、IoU_E都小于iou_thresh,为了不丢失这个训练样本,我们选择选择IoU值最大的先验框P_A。将P_A对应的预测框B_A,标记为正样本,即先验框决定哪些预测框会参与到何种损失的计算中去
  • 第2种情况:仅有一个IoU值大于iou_thresh,那么这个先验框所对应的预测框会被标记为正样本,会参与到置信度、类别及位置损失的计算。
  • 第3种情况:有多个IoU值大于iou_thresh,那么这些先验框所对应的预测框都会被标记为正样本,即一个目标会被匹配上多个正样本

这种正样本匹配策略,似乎保证了每个目标都会至少匹配上一个正样本,但其实存在漏洞。假如,有2个目标的中心点都落到了同一个目标框,可能会导致原本属于目标A的先验框后来又分配给目标B

  • 在YOLOv1中,2个目标的中心点都落到了同一个目标框,网络就只能学习一个。
  • 在YOLOv2中,虽然每个网格会输出多个预测框,但是在制作正样本时候,也会存在刚才说的语义歧义现象,会使得某些目标匹配不到正样本,其信息也就不会被网络学习到,不过我们现在不做处理。

在这里插入图片描述

1.2 代码实现

1.2.1 正样本匹配

pytorch读取VOC数据集:

  • 一批图像数据的维度是 [B, 3, H, W] ,分别是batch size,色彩通道数,图像的高和图像的宽。

  • 标签数据是一个包含 B 个图像的标注数据的python的list变量(如下所示),其中,每个图像的标注数据的list变量又包含了 M 个目标的信息(类别和边界框)。

  • 获得了这一批数据后,图片是可以直接喂到网络里去训练的,但是标签不可以,需要再进行处理一下。

[{'boxes':     tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)'labels':    tensor([18.]),                       # 标签'orig_size': [281, 500]                           # 图片的原始大小}, {'boxes':      tensor([[  0.,  79., 416., 362.]]), 'labels':     tensor([1.]),'orig_size': [375, 500]}
]

标签处理主要包括3个部分,

  • 一是将真实框中心所在网格对应正样本位置(anchor_idx)的置信度置为1,其他默认为0
  • 二是将真实框中心所在网格对应正样本位置(anchor_idx)的标签类别为1(one-hot格式),其他类别设置为0
  • 三是将真实框中心所在网格对应正样本位置(anchor_idx)的bbox信息设置为真实框的bbox信息。
# 处理好的shape如下:
# gt_objectness  
torch.Size([2, 845, 1])  # 845=13×13×5
# gt_classes
torch.Size([2, 845, 20])
# gt_bboxes
torch.Size([2, 845, 4])

1.2.2 具体代码实现

# RT-ODLab/models/detectors/yolov2/matcher.pyimport torch
import numpy as npclass Yolov2Matcher(object):def __init__(self, iou_thresh, num_classes, anchor_size):self.num_classes = num_classesself.iou_thresh = iou_thresh# anchor boxself.num_anchors = len(anchor_size)self.anchor_size = anchor_sizeself.anchor_boxes = np.array([ [0., 0., anchor[0], anchor[1]] for anchor in anchor_size])  # [KA, 4]def compute_iou(self, anchor_boxes, gt_box):"""函数功能: 计算目标框和5个先验框的IoU值anchor_boxes : ndarray -> [KA, 4] (cx, cy, bw, bh).gt_box : ndarray -> [1, 4] (cx, cy, bw, bh).返回值: iou变量,类型为ndarray类型,shape为[5,], iou[i]就表示该目标框和第i个先验框的IoU值"""# 1、计算5个anchor_box的面积# anchors: [KA, 4]anchors = np.zeros_like(anchor_boxes)anchors[..., :2] = anchor_boxes[..., :2] - anchor_boxes[..., 2:] * 0.5  # x1y1anchors[..., 2:] = anchor_boxes[..., :2] + anchor_boxes[..., 2:] * 0.5  # x2y2anchors_area = anchor_boxes[..., 2] * anchor_boxes[..., 3]# 2、gt_box复制5份,计算5个相同gt_box的面积# gt_box: [1, 4] -> [KA, 4]gt_box = np.array(gt_box).reshape(-1, 4)gt_box = np.repeat(gt_box, anchors.shape[0], axis=0)gt_box_ = np.zeros_like(gt_box)gt_box_[..., :2] = gt_box[..., :2] - gt_box[..., 2:] * 0.5  # x1y1gt_box_[..., 2:] = gt_box[..., :2] + gt_box[..., 2:] * 0.5  # x2y2gt_box_area = np.prod(gt_box[..., 2:] - gt_box[..., :2], axis=1)# 3、计算计算目标框和5个先验框的IoU值# intersection  交集inter_w = np.minimum(anchors[:, 2], gt_box_[:, 2]) - \np.maximum(anchors[:, 0], gt_box_[:, 0])inter_h = np.minimum(anchors[:, 3], gt_box_[:, 3]) - \np.maximum(anchors[:, 1], gt_box_[:, 1])inter_area = inter_w * inter_h# unionunion_area = anchors_area + gt_box_area - inter_area# iouiou = inter_area / union_areaiou = np.clip(iou, a_min=1e-10, a_max=1.0)return iou@torch.no_grad()def __call__(self, fmp_size, stride, targets):"""img_size: (Int) input image sizestride: (Int) -> stride of YOLOv1 output.targets: (Dict) dict{'boxes': [...], 'labels': [...], 'orig_size': ...}"""# preparebs = len(targets)fmp_h, fmp_w = fmp_sizegt_objectness = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, 1]) gt_classes = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, self.num_classes]) gt_bboxes = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, 4])# 第一层for循环遍历每一张图像的标签for batch_index in range(bs):# targets_per_image是python的Dict类型targets_per_image = targets[batch_index]# [N,] N表示一个图像中有N个目标对象tgt_cls = targets_per_image["labels"].numpy()# [N, 4]tgt_box = targets_per_image['boxes'].numpy()# 第二层for循环遍历这张图像标签的每一个目标数据for gt_box, gt_label in zip(tgt_box, tgt_cls):x1, y1, x2, y2 = gt_box# xyxy -> cxcywhxc, yc = (x2 + x1) * 0.5, (y2 + y1) * 0.5bw, bh = x2 - x1, y2 - y1gt_box = [0, 0, bw, bh]# checkif bw < 1. or bh < 1.:continue    # 1、计算该目标框和5个先验框的IoU值iou = self.compute_iou(self.anchor_boxes, gt_box)iou_mask = (iou > self.iou_thresh)# 2、基于先验框的标签分配策略label_assignment_results = []# 第一种情况:所有的IoU值均低于阈值,选择IoU最大的先验框if iou_mask.sum() == 0:# We assign the anchor box with highest IoU score.iou_ind = np.argmax(iou)anchor_idx = iou_ind# compute the grid cellxc_s = xc / strideyc_s = yc / stridegrid_x = int(xc_s)grid_y = int(yc_s)label_assignment_results.append([grid_x, grid_y, anchor_idx])else:# 第二种和第三种情况:至少有一个IoU值大于阈值for iou_ind, iou_m in enumerate(iou_mask):if iou_m:anchor_idx = iou_ind# compute the gride cellxc_s = xc / strideyc_s = yc / stridegrid_x = int(xc_s)grid_y = int(yc_s)label_assignment_results.append([grid_x, grid_y, anchor_idx])# label assignment# 获取到被标记为正样本的先验框,我们就可以为这次先验框对应的预测框制作学习标签for result in label_assignment_results:grid_x, grid_y, anchor_idx = resultif grid_x < fmp_w and grid_y < fmp_h:# objectness标签,采用0,1离散值gt_objectness[batch_index, grid_y, grid_x, anchor_idx] = 1.0# classification标签,采用one-hot格式cls_ont_hot = np.zeros(self.num_classes)cls_ont_hot[int(gt_label)] = 1.0gt_classes[batch_index, grid_y, grid_x, anchor_idx] = cls_ont_hot# box标签,采用目标框的坐标值gt_bboxes[batch_index, grid_y, grid_x, anchor_idx] = np.array([x1, y1, x2, y2])# [B, H, W, A, C] -> [B, HWA, C]gt_objectness = gt_objectness.reshape(bs, -1, 1)gt_classes = gt_classes.reshape(bs, -1, self.num_classes)gt_bboxes = gt_bboxes.reshape(bs, -1, 4)# to tensorgt_objectness = torch.from_numpy(gt_objectness).float()gt_classes = torch.from_numpy(gt_classes).float()gt_bboxes = torch.from_numpy(gt_bboxes).float()return gt_objectness, gt_classes, gt_bboxesif __name__ == '__main__':anchor_size  = [[17, 25], [55, 75], [92, 206], [202, 21], [289, 311]]matcher = Yolov2Matcher(iou_thresh=0.5, num_classes=20, anchor_size=anchor_size)targets = [{'boxes':     torch.tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)'labels':    torch.tensor([18.]),                       # 标签'orig_size': [281, 500]                                 # 图片的原始大小},{'boxes':      torch.tensor([[  0.,  79., 416., 362.]]),'labels':     torch.tensor([1.]),'orig_size': [375, 500]}
]gt_objectness, gt_classes, gt_bboxes = matcher(fmp_size=(13, 13),stride=32, targets=targets )print(gt_objectness.shape)print(gt_classes.shape)print(gt_bboxes.shape)
  • 最终这段代码返回了gt_objectness, gt_classes, gt_bboxes三个Tensor类型的变量:
    • gt_objectness包含一系列的0和1,标记了哪些预测框是正样本,哪些预测框是负样本
    • gt_classes包含一系列的one-hot格式的类别标签
    • gt_bboxes包含的是正样本要学习的边界框的位置参数
  • 在上述代码实现中,在计算IoU时候,我们将目标框的中心点坐标和先验框的中心点坐标都设置为0,这是因为一个目标框在做匹配时候,仅仅考虑到目标框中心点所在的网格中的5个先验框,周围的网格都不进行考虑
  • 在SSD以及Faster R-CNN中,每一个目标框都是和全局的先验框去计算IoU,这些算法都会考虑目标框的中心点坐标和先验框的中心点坐标。因此,其每一个目标框匹配上的先验框不仅来自中心点所在的网格,也会来自周围的网格。这是YOLO和其他工作一个重要差别所在,YOLO这种只考虑中心点的做法,处理起来更加简便、更易学习。

2 损失函数的计算、YOLOv2的训练

2.1 损失函数的计算

  • YOLOv2损失函数计算(RT-ODLab/models/detectors/yolov2/loss.py)和之前实现的YOLOv1基本一致,不再赘述
  • 我们实现的YOLOv2和之前实现的YOLOv1相比,仅仅多了先验框以及由此带来的正样本匹配上的一些细节上的差别。

2.2 YOLOv2的训练

  • 完成了YOLOv2的网络搭建,标签匹配以及损失函数的计算,就可以进行训练了

  • 数据读取、数据预处理及数据增强操作,和之前实现的YOLOv1一致,不再赘述

  • YOLOv1和YOLOv2都在同一个项目代码中,数据代码、训练代码及测试代码均一致,我们只需要修改训练脚本即可

    nohup python -u train.py --cuda \-d voc                 \-m yolov2              \-bs 16                 \-size 640              \--wp_epoch 3           \--max_epoch 150        \--eval_epoch 10        \--no_aug_epoch 10      \--ema                  \--fp16                 \--multi_scale          \--num_workers 8 1>./logs/yolo_v2_train_log.txt 2>./logs/yolo_v2_warning_log.txt &
    

相关参数讲解可以参考YOLOv1:

经典目标检测YOLO系列(一)复现YOLOV1(5)模型的训练及验证

2.3 可视化检测结果、计算mAP指标

  • 训练结束后,模型默认保存在weights/voc/yolov2/文件夹下,名为yolov2_voc_best.pth,保存了训练阶段在测试集上mAP指标最高的模型。

  • 运行项目中所提供的eval.py文件可以验证模型的性能,具体命令如下行所示

  • 可以给定不同的图像尺寸来测试实现的YOLOv1在不同输入尺寸下的性能

    python eval.py \
    --cuda -d voc \
    --root path/to/voc -m yolov2 \
    --weight path/to/yolov2_voc_best.pth \
    -size 416
    
  • 也可以可视化训练好的模型

    python test.py \
    --cuda -d voc \
    --root path/to/voc -m yolov2 
    --weight path/to/yolov2_voc_best.pth \
    -size 416 -vt 0.3 \
    --show# -size表示输入图像的最大边尺寸
    # -vt是可视化的置信度阈值,只有高于此值的才会被可视化出来
    # --show表示展示检测结果的可视化图片
    

2.4 训练结果

《YOLO目标检测》作者训练好的模型,在VOC2007测试集测试指标如下:

从表格中可以看到,实现的YOLOv2达到了官方YOLOv2的性能。

模型输入尺寸mAP(%)
YOLOv2*(官方)41676.8
YOLOv2*(官方)48077.8
YOLOv2*(官方)54478.6
YOLOv241676.8
YOLOv248078.4
YOLOv254479.6
YOLOv264079.8

相关文章:

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练 我们在之前实现YOLOv1的基础上&#xff0c;加入了先验框机制&#xff0c;快速的实现了YOLOv2的网络架构&#xff0c;并且实现了前向推理过程。 经典目标检测YOLO系列(二)YOLOV2的复现(1)总体…...

半波整流电路原理详解+参数与计算公式

什么是半波整流电路&#xff1f; 半波整流电路的基本操作非常简单&#xff0c;输入信号通过二极管&#xff0c;由于只能通过一个方向的电流&#xff0c;二极管的整流作用&#xff0c;单个二极管只允许通过一半的波形。 下图说明了半波整流电路的基本原理。 半波整流电路工作图…...

GZ036 区块链技术应用赛项赛题第3套

2023年全国职业院校技能大赛 高职组 “区块链技术应用” 赛项赛卷&#xff08;3卷&#xff09; 任 务 书 参赛队编号&#xff1a; 背景描述 新能源作为新兴领域&#xff0c;产业呈现碎片化与复杂化的特性&#xff0c;逐渐出现管理困难、供应链金融、可信监管与数…...

LeetCode142.环形链表II

力扣题目链接 思路&#xff1a;判断链表是否有环&#xff1f;可以使用快慢指针法&#xff0c;快指针每次走两步&#xff0c;慢指针每次走一步&#xff0c;如果链表有环一定会在环中相遇。 如何找环的入口&#xff1f;当快慢指针在环中第一次相遇时&#xff0c;让快指针从头结…...

触摸按键控制LED灯

目录 1.理论 2.代码 2.1 touch_ctrl_led.v 2.2 tb_touch_ctrl_led 1.理论 以上的波形图的touch_flag是采用组合逻辑的方式产生的。 以上的touch_flag是采用时序逻辑产生的&#xff0c;时序逻辑会延迟一拍。 以上是上升沿和下降沿的组合逻辑和时序逻辑实现&#xff0c;逻辑或…...

QT自定义控件0-360°刻度尺

支持0到360&#xff0c;360到0的过度。 直接上代码&#xff0c;可以直接用&#xff0c;使用的paintevent事件实现的&#xff0c;没啥好讲的。 .cpp void Widget::drawCourse(QPainter& p,QPen pen,QFont font) {double currentNumber m_ang;p.setBrush(Qt::black);p.dra…...

c语言0基础笔记

目录 前言 第01章_C语言入门 1.1初识计算机语言 1.2初识C语言 1.3第一个c程序 1.4IDE使用 1.5注释 1.6第一个c程序剖析 1.7printf()输出格式 第02章_变量与进制 2.1关键字 2.2标识符 2.3变量 2.4基本数据类型的使用 2.5变量间的运算规则 2.6常量 2.7输入/输出函…...

Vue 中 Element UI 的 el-table 组件实现动态表头和内容

在 Vue 中使用 Element UI 的 el-table 组件时&#xff0c;为了实现动态表头&#xff08;包括第一层表头及其下的嵌套表头或子表头&#xff09;。需要后端返回的数据结构能够体现表头层级关系以及对应的数据结构相匹配。这样的数据通常是一个嵌套数组&#xff0c;每个表头单元可…...

安装sqlserver后—无法连接到 127.0.0.1,1433\sqlexpress

报错问题如下&#xff1a; 标题: 连接到服务器 ------------------------------ 无法连接到 127.0.0.1,1433\sqlexpress。 ------------------------------ 其他信息: 登录失败。该登录名来自不受信任的域&#xff0c;不能与 Windows 身份验证一起使用。 (Microsoft SQL Serve…...

Python JSON解析校验格式,输出错误信息的工具

引言&#xff1a; 在现代软件开发中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;被广泛应用于前后端数据传输和存储。然而&#xff0c;由于JSON的灵活性和复杂性&#xff0c;解析JSON时常常会遇到格式错误的问…...

物联网网关与plc怎么连接?

物联网网关与plc怎么连接&#xff1f; 物联网是当今社会中最热门的技术之一&#xff0c;而物联网网关则是连接物联网设备与云平台的核心设备之一。物联网网关在连接各种传感器和设备时起着至关重要的作用。而另一种广泛应用于工业控制和自动化领域的设备是可编程逻辑控制器&…...

HANA:存储过程(Procedures) DEBUG

作者 idan lian 如需转载备注出处 如果对你有帮助&#xff0c;请点赞收藏~~~ 1.场景 最近不是写了蛮多hana的存储过程吗&#xff0c;如果是简单的增删改查&#xff0c;如果结果错了&#xff0c;还是比较容易找到错误在哪的&#xff0c;但是逐渐假如循环啊&#xff0c;变量判…...

Oracle行转列函数,列转行函数

Oracle行转列函数&#xff0c;列转行函数 Oracle 可以通过PIVOT,UNPIVOT,分解一行里面的值为多个列,及来合并多个列为一行。 PIVOT PIVOT是用于将行数据转换为列数据的查询操作(类似数据透视表)。通过使用PIVOT&#xff0c;您可以按照特定的列值将数据进行汇总&#xff0c;并将…...

线程同步--生产者消费者模型

文章目录 一.条件变量pthread线程库提供的条件变量操作 二.生产者消费者模型生产者消费者模型的高效性基于环形队列实现生产者消费者模型中的数据容器 一.条件变量 条件变量是线程间共享的全局变量,线程间可以通过条件变量进行同步控制条件变量的使用必须依赖于互斥锁以确保线…...

React hook+AntD pro实现Form表单的二次封装

React hookAntD pro实现Form表单的二次封装 封装Form表单1、在src/types下新建 antd/form/index.ts&#xff0c;进行Form表的配置、数据等类型的限制2、在 根目录/components 下新建 BaseForm/index.tsx文件3、在BaseForm/createFormIpt.tsx中&#xff0c;抽取对不同类型的表单…...

python异步切片下载文件(内置redis获取任务 mongo更新任务状态等)

异步切片下载二进制文件并上传桶删除本地文件 import json import os import asyncio from urllib import parseimport aiohttp import aioredis from motor.motor_asyncio import AsyncIOMotorClient from retrying import retry from minio import Minio from minio.error im…...

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(10)-Fiddler如何设置捕获Firefox浏览器的Https会话

1.简介 经过上一篇对Fiddler的配置后&#xff0c;绝大多数的Https的会话&#xff0c;我们可以成功捕获抓取到&#xff0c;但是有些版本的Firefox浏览器仍然是捕获不到其的Https会话&#xff0c;需要我们更进一步的配置才能捕获到会话进行抓包。 2.宏哥环境 1.宏哥的环境是Wi…...

阿里云云原生弹性方案:用弹性解决集群资源利用率难题

作者&#xff1a;赫曦 随着上云的认知更加普遍&#xff0c;我们发现除了以往占大部分的互联网类型的客户&#xff0c;一些传统的企业&#xff0c;一些制造类的和工业型企业客户也都开始使用云原生的方式去做 IT 架构的转型&#xff0c;提高集群资源使用率也成为企业上云的一致…...

Spring-BeanPostProcessor PostConstruct init InitializingBean 执行顺序

执行顺序探究 新建一个对象用于测试 Component public class Student implements InitializingBean {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}pu…...

【算法】递归

递归 递归初始递归&#xff1a;数列求和递归的应用&#xff1a;任意进制转换递归深度限制递归可视化&#xff1a;分形树递归可视化&#xff1a;谢尔宾斯基Sierpinski三角形递归的应用&#xff1a;汉诺塔递归的应用&#xff1a;探索迷宫 分治策略和递归优化问题兑换最少个数硬币…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...