【多模态融合】TransFusion学习笔记(1)

    工作上主要还是以纯lidar的算法开发,部署以及系统架构设计为主。对于多模态融合(这里主要是只指Lidar和Camer的融合)这方面研究甚少。最近借助和朋友们讨论论文的契机接触了一下这方面的知识,起步是晚了一点,但好歹是开了个头。下面就借助TransFusion论文及其开源代码来串一串这方面的知识。TransFusion文中指出已经存在的Lidar-Camera的融合方法大致可以分为3类:

Result-Level:这个很好理解,参考FPointNet网络图示。就是利用现有的2D检测器在图像上提取2D初始框,根据Camera成像原理,捕获2D框视锥内的点云,再利用点云分类模型(如:PointNet)做进一步分类。

这个方法的好处是显然的,遥想作者当年发论文的时候,图像的2D检测相对已经成熟,但是点云3D检测才刚刚起步(好吧,其实也是该作者开创了点云深度学习处理的先河),以2D box作为Query应该说是一个很不错的思路。很多后续论文在谈论FPointNet时说该方法受限于图像2D检测模型。如果2D检测露检则3D部分也不可能恢复。

Proposal-Level:提到Proposal我们很容易想到faster-rcnn这种经典网络,它由第一阶段中的RPN提供候Proposal,第二阶段利用proposal以及RoiPooling(或者后续该进型的RoiAlign等)操作提取proposal region-wise features后再对box做一次精修。具体到多模态的Proposal-Level的融合,你可以想象为其中一个模态的模型作为第一阶段先提供Proposal,然后各个模态以这个Proposal为媒介进行特征融合等。MV3D算是这方面的开山之作,其BEV下的lidar检测模型先提供一下proposal,然后这个proposal分别映射到Lidar BEV view,Lidar Front view以及Image View 这3张特征图上去提取region-wise features进行融合。此时的融合已经是一种基于Proposal的特征级融合了。有了这个融合后的特征,自然就可以继续再接分类,回归等任务。   

Point-Level:原始lidar点云通常包含3个维度的坐标(x,y,z)信息再加上1维强度信息。但是借助Lidar和Camera之间的标定关系,可以建立point到pixel的联系。Point-Level的融合就是借助了这类联系对lidar点云的特征进行扩充。扩充的特征有可能是point对应pixel的分割label,如大名鼎鼎的PointPainting,操作又简单,又通用,提点能力也杠杠的。

后来发展到既然你可以Painting分割的label,为什么我不可以Painting分割/检测的feature。既然你可以Painting某一层的feature,我为什么不可以painting multi-scale的feature等等。当然,还有更多的复杂一些的操作,涉及到不同view下的Painting,以及由点的Painting到voxel的Painting等等,但是总体思想不变。这种Point-Level的方法存在几个问题,一个就是对标定的准确性要求很高,因为就是点对点的。另外一个点云毕竟是稀疏的,如果只是利用了point2pixel部分的图像点的信息,则图像信息的利用是严重不充分的。毕竟你是需要额外整出一个模型来提取图像的分割label或者特征的,开销不小。此外,低质量图像带来的错误的分割label或者干扰性的特征反而会影响到点云的模型效果。说了这么多那TransFusion这篇论文有何特别之处,按作者原文的说法是"Our key idea is to reposition the focus of the fusion process,from hard-association to soft-association,leading to the robustness against degenerate image quality and sensor misalignment".字面意思就是TransFusion使用一种软关联机制(soft-association)取代以往融合方法中的硬关联机制,这样使得融合算法在图像质量退化和传感器没有严格对齐的情况下能更加鲁棒。至于这种软关联机制具体怎么实现,我觉得可以结合论文和代码先分析TransFusion-L这一纯Lidar的3D检测,再分析融合了图像后的完整的框架,可能会有更好的理解。

TransFusion-L

TransFusion多模态融合框架中纯lidar检测部分称其为TransFusion-L。从下表中可见,在nuScenes测试集上,单就纯Lidar的方法PK,TransFusion-L相比CenterPoint模型mAP和NDS都有大幅提升。

TransFusion-L的pipeline如下图所示,我从多模态pipeline中去掉了图像融合的元素。乍一看就是将CenterPoint的检测头换成了TransFormer的检测头,但是其中仍然有诸多创新之处。

他借鉴了最新的基于Transfomer的图像2D检测的方法,使用稀疏的object query通过transformer的方式来聚合特征并预测最终的目标。在object query的初始化上不仅作到了input-dependent,而且还具备category-aware。从论文后的对比实验来看这是2个极其重要的特性。表格中a)为完整的TransFusion-L,b)表示去掉了category-aware。mAP和NDS分别掉了5.7%和2.8%。d)表示去掉了input-dependent,e...这个结果就完全没法看了。即使通过增加Decoder Layers的层数来学习弥补也远落后于仅有一层Decode Layer的TransFusion-L。

那Input-Depentdent和Class-Aware分别又是什么呢?

Input-Dependent:

在DETR这一将Transformer用于目标检测的开山之作中首次引入了object queries的概念。

这些个object  queries是可学习的embedding,从论文代码中可见query_embed初始是一个Shape为(num_queris, hidden_dim)的embedding,在模型训练过程中再不断迭代更新参数。

一旦模型训练完成query参数也就固定了,推理过程中与当前输入图像的内容并无关系,也就是不由当前图像内容计算得到。为什么这样的object query还能work呢?首先,上面也说了object query虽然是随机初始化,但是会随着网络训练而更新。不同object queries通过self-attention建模了patter与patter之间的关系,通过cross-attention又从transformer-encoder序列中对patter相关特征做了聚合。

上面这种形式的object query称其为input-independent query。现在我们再来看TransFusion-L中的Input-Dependent的object query就好理解了,它再也不需要单独随机初始化一组object query embedding再通过训练网络来更新参数,而是直接使用LiDAR BEV Features来初始化。有个问题就是Lidar BEV Features是一个dense的feature map,而我们要选择的object query是稀疏的,最终哪些位置上的feature用来初始化query? 这个就交给heatmap来选择了。

这个操作很秀,通过heatmap筛选出热点,既保证了object query的稀疏性,同时又可以由热点位置的feature来进行初始化,同时热点的位置天然可以用于Position-Encoding(位置编码)加入到query中。输入图像的内容不同,得到的bev feature map不同,heatmap也不同,自然object query也不一样,所以就是Input-Dependent的了。通过这种方式初始化的query会更加贴近潜在目标的中心,而不需要像DETR那样通过multiple decoder layers堆叠来修正位置。

Class-Aware:heatmap中得到的热点除了位置和特征信息,同时还包含了分类信息。类似位置编码,对one-hot类别向量进行编码也加入到query特征中。加入了类别编码信息的query对self-attention阶段建模object-object之间的联系,以及cross-attention阶段建模object-context之间的联系大有裨益。

下面通过TransFusion检测头部分的代码对Input-Dependent和Class-Aware做进一步的理解。因为还是讨论TransFusion-L所以会先屏蔽图像融合部分的代码。

@HEADS.register_module()
class TransFusionHead(nn.Module):def __inti__(self,...):super(TransFusionHead,self).__init__()#......def forward_single(self, inputs, img_inputs, img_metas):#......def forward(self, feats, img_feats, img_metas):if img_feats is None:img_feats = [None]res = multi_apply(self.forward_single, feats, img_feats, [img_metas])assert len(res) == 1, "only support one level features."return res

TransFusion-L总的pipeline大该为Input-->预处理-->3D Backbone-->Height Compression-->2D Neck-->Head。为方便调试,假定batch为2,其它按默认配置设置则进入到head的feats维度为[2,512,128(H),128(W)]。其实在HeightCompression完成高度压缩以后就可以类似处理2D图像的feature map进行处理了。forward之后会立刻进入forward_single函数,也是整个head的核心。

源文件:mmdet3d/models/dense_heads/transfusion_head.py

  1  def forward_single(self, inputs, img_inputs, img_metas):                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        2         """Forward function for CenterPoint.3         Args:4             inputs (torch.Tensor): Input feature map with the shape of5                 [B, 512, 128(H), 128(W)]. (consistent with L748)6  7         Returns:8             list[dict]: Output results for tasks.9         """10         batch_size = inputs.shape[0]11         ##[2, 128, 128, 128])12         lidar_feat = self.shared_conv(inputs)    ##3x3普通卷积13  14         #################################15         # 16         #################################17         lidar_feat_flatten = lidar_feat.view(batch_size, lidar_feat.shape[1], -1)  # [BS, C, H*W]18         bev_pos = self.bev_pos.repeat(batch_size, 1, 1).to(lidar_feat.device)         ##bev_pos只是网格坐标...19  20         if self.fuse_img:21             ##pass22  23         #################################24         # image guided query initialization25         #################################26         if self.initialize_by_heatmap:27             ##torch.Size([2, 10, 128, 128]) <==28             dense_heatmap = self.heatmap_head(lidar_feat)29             dense_heatmap_img = None30             if self.fuse_img:31                 ###32                 ###33             else:34                 heatmap = dense_heatmap.detach().sigmoid()35             padding = self.nms_kernel_size // 236             local_max = torch.zeros_like(heatmap)37             # equals to nms radius = voxel_size * out_size_factor * kenel_size38             local_max_inner = F.max_pool2d(heatmap, kernel_size=self.nms_kernel_size, stride=1, padding=0)39             local_max[:, :, padding:(-padding), padding:(-padding)] = local_max_inner40             ## for Pedestrian & Traffic_cone in nuScenes41             if self.test_cfg['dataset'] == 'nuScenes':42                 local_max[:, 8, ] = F.max_pool2d(heatmap[:, 8], kernel_size=1, stride=1, padding=0)43                 local_max[:, 9, ] = F.max_pool2d(heatmap[:, 9], kernel_size=1, stride=1, padding=0)44             elif self.test_cfg['dataset'] == 'Waymo':  # for Pedestrian & Cyclist in Waymo45                 local_max[:, 1, ] = F.max_pool2d(heatmap[:, 1], kernel_size=1, stride=1, padding=0)46                 local_max[:, 2, ] = F.max_pool2d(heatmap[:, 2], kernel_size=1, stride=1, padding=0)47             heatmap = heatmap * (heatmap == local_max)48             heatmap = heatmap.view(batch_size, heatmap.shape[1], -1)49  50             # top #num_proposals among all classes51             top_proposals = heatmap.view(batch_size, -1).argsort(dim=-1, descending=True)[..., :self.num_proposals]52             top_proposals_class = top_proposals // heatmap.shape[-1]53  54             top_proposals_index = top_proposals % heatmap.shape[-1]55             query_feat = lidar_feat_flatten.gather(index=top_proposals_index[:, None, :].expand(-1, lidar_feat_flatten.shape[1], -1), dim=-1)56             self.query_labels = top_proposals_class57  58             # add category embedding59             one_hot = F.one_hot(top_proposals_class, num_classes=self.num_classes).permute(0, 2, 1)60             query_cat_encoding = self.class_encoding(one_hot.float())61             query_feat += query_cat_encoding62             query_pos = bev_pos.gather(index=top_proposals_index[:, None, :].permute(0, 2, 1).expand(-1, -1, bev_pos.shape[-1]), dim=1)63         else:64             query_feat = self.query_feat.repeat(batch_size, 1, 1)  # [BS, C, num_proposals]65             base_xyz = self.query_pos.repeat(batch_size, 1, 1).to(lidar_feat.device)  # [BS, num_proposals, 2]66  67         #################################68         # transformer decoder layer (LiDAR feature as K,V)69         #################################70         ret_dicts = []71         for i in range(self.num_decoder_layers):72             prefix = 'last_' if (i == self.num_decoder_layers - 1) else f'{i}head_'73  74             # Transformer Decoder Layer75             # :param query: B C Pq    :param query_pos: B Pq 3/676             query_feat = self.decoder[i](query_feat, lidar_feat_flatten, query_pos, bev_pos)77  78             # Prediction79             res_layer = self.prediction_heads[i](query_feat) ##FFN80  81             ##这个center是一个相对的偏移,所以要通过下面的方式计算出实际预测的center82             res_layer['center'] = res_layer['center'] + query_pos.permute(0, 2, 1)83             first_res_layer = res_layer84             if not self.fuse_img:85                 ret_dicts.append(res_layer)86  87             # for next level positional embedding88             query_pos = res_layer['center'].detach().clone().permute(0, 2, 1)89  90         #################################91         # transformer decoder layer (img feature as K,V)92         #################################93         if self.fuse_img:94             pass95  96         if self.initialize_by_heatmap:97             ret_dicts[0]['query_heatmap_score'] = heatmap.gather(index=top_proposals_index[:, None, :].expand(-1, self.num_classes, -1), dim=-1)  # [bs, num_classes, num_proposals]98             if self.fuse_img:99                 ret_dicts[0]['dense_heatmap'] = dense_heatmap_img
100             else:
101                 ret_dicts[0]['dense_heatmap'] = dense_heatmap
102  
103         if self.auxiliary is False:
104             # only return the results of last decoder layer
105             return [ret_dicts[-1]]
106  
107         # return all the layer's results for auxiliary superivison
108         new_res = {}
109         for key in ret_dicts[0].keys():
110             if key not in ['dense_heatmap', 'dense_heatmap_old', 'query_heatmap_score']:
111                 new_res[key] = torch.cat([ret_dict[key] for ret_dict in ret_dicts], dim=-1)
112             else:
113                 new_res[key] = ret_dicts[0][key]
114         return [new_res]

    第12行,通过一个普通的3x3卷积将输入通道数从512压缩到128;

    第18行,将self.bev_pos也就是bev下的feature map网格坐标复制batch份。self.bev_pos在初始__init__函数中通过create_2D_grid函数创建。create_2D_grid顾名思义就是创建2D的网格,他会返回一整张网格map的中心点坐标。

def create_2D_grid(x_size, y_size):meshgrid = [[0, x_size - 1, x_size], [0, y_size - 1, y_size]]batch_y, batch_x = torch.meshgrid(*[torch.linspace(it[0], it[1], it[2]) for it in meshgrid])batch_x = batch_x + 0.5batch_y = batch_y + 0.5coord_base = torch.cat([batch_x[None], batch_y[None]], dim=0)[None]coord_base = coord_base.view(1, 2, -1).permute(0, 2, 1)return coord_baseif __name__ == "__main__":x_size = 128y_size = 128coord_base = create_2D_grid(x_size,y_size)print('x_size:{},y_size:{}'.format(x_size,y_size))print('coord_base shape:', coord_base.shape)print('coord_base:', coord_base)

运行:

x_size:128,y_size:128
coord_base shape: torch.Size([1, 16384, 2])
coord_base: tensor([[[  0.5000,   0.5000],[  1.5000,   0.5000],[  2.5000,   0.5000],...,[125.5000, 127.5000],[126.5000, 127.5000],[127.5000, 127.5000]]])

    第26行,进入heatmap初始化逻辑中;

    第28行,输入lidar bev特征图(2,128,128,128)通过若干卷积操作生成热力图(2,10,128,128)。我们以nuScenes数据集为例,10为类别数量;

    第38行,使用torch function自带的max_pool2d函数,结合kernel_size(self.nms_kernel_size默认为3)等参数取heatmap局部邻域(kernel)内的最大之值;

    第39行,因为heatmap做max_pool2d的时候padding为0,所以得到的local_max_inner的shape减了2,所以往local_max里面做填充的时候需要H,W维度需要给一个padding:(-padding)的范围;

    第47行,经过该操作邻域内不是最热的地方就被置为0了;

    第50行,通过对热度其实就是类别score由高到低排序,取出前self.num_proposals(默认200)的索引位置,保存到top_proposals中;

    第51-52行,分别计算出top_proposal代表的类别和所在通道内的索引;

    第55行,借助上一步计算得到的热点top_proposals_index从lidar_feat_flatten中收集特征,这些收集到的特征用于初始化object query。当然,在此基础上还会再sum上类别编码和位置编码特征;

    第59-60行,就是完成object query的"class-aware"特性最重要的两步,将top_proposals_class的类别id做one-hot,并进行类别编码。所谓的self.class_encoding也就是一个一维的卷积;

    第71-88行,这个就是经典的transformer decoder + ffn操作了。因为有了input-dependent和class-aware两大法宝的加持,decoder的层数(self.num_decoder_layers)可以直接缩减到一层了;

至此,TransFusion-L的核心部分就完成了,相比他的图像融合部分还是好理解的。我在浏览其它相关文章的时候也注意到图森有一篇思想比较相近的文章CenterFormer。

不考虑他mutli-frame的部分的话,主要是加入了一个multi-scale的概念。其实就是对2d neck网络部分以及K,V的选取方式做了调整。因为2d neck部分是一个multi scale的网络,object query就可以从不同scale的feature map上聚合K,V效果又可以有提升。

    

                                      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/148243.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GEO生信数据挖掘(五)提取临床信息构建分组,分组数据可视化(绘制层次聚类图,绘制PCA图)

检索到目标数据集后&#xff0c;开始数据挖掘&#xff0c;本文以阿尔兹海默症数据集GSE1297为例 上节做了很多的基因数据清洗&#xff08;离群值处理、低表达基因、归一化、log2处理&#xff09;操作&#xff0c;本节介绍构建临床分组信息。 我们已经学习了提取表达矩阵的临床…

蓝桥杯每日一题2023.10.4

双向排序 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 六十分解法如下&#xff1a;按照题意简单排序 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, m, p, q, a[N]; bool cmp(int x, int y) {return x > y; } int main() {cin >&g…

postgresql-备份与恢复

postgresql-备份与恢复 基本概念备份类型物理备份与逻辑备份在线备份与离线备份全量备份与增量备份 备份恢复工具备份与恢复逻辑备份与还原备份单个数据库psqlpg_dumppg_store 备份整个集群 基本概念 服务器系统错误、硬件故障或者人为失误都可能导致数据的丢失或损坏。因此&am…

【Java 进阶篇】JDBC 数据库连接池详解

数据库连接池是数据库连接的管理和复用工具&#xff0c;它可以有效地降低数据库连接和断开连接的开销&#xff0c;提高了数据库访问的性能和效率。在 Java 中&#xff0c;JDBC 数据库连接池是一个常见的实现方式&#xff0c;本文将详细介绍 JDBC 数据库连接池的使用和原理。 1…

Qt扩展-QCustomPlot绘图基础概述

QCustomPlot绘图基础概述 一、概述二、改变外观1. Graph 类型2. Axis 坐标轴3. 网格 三、案例1. 简单布局两个图2. 绘图与多个轴和更先进的样式3. 绘制日期和时间数据 四、其他Graph&#xff1a;曲线&#xff0c;条形图&#xff0c;统计框图&#xff0c;… 一、概述 本教程使用…

调度程序以及调度算法的评价指标

1.调度器/调度程序 调度程序决定调度算法&#xff0c;时间片大小 ②&#xff0c;③由调度程序引起&#xff0c;调度程序决定: 1.调度时机 创建新进程进程退出运行进程阻塞I/O中断发生&#xff08;可能唤醒某些阻塞进程)非抢占式调度策略&#xff0c;只有运行进程阻塞或退出…

小谈设计模式(14)—建造者模式

小谈设计模式&#xff08;14&#xff09;—建造者模式 专栏介绍专栏地址专栏介绍 建造者模式角色分类产品&#xff08;Product&#xff09;抽象建造者&#xff08;Builder&#xff09;具体建造者&#xff08;Concrete Builder&#xff09;指挥者&#xff08;Director&#xff0…

10.5作业

磕磕绊绊还是差不多完成了,tcp多客户端在线词典 代码&#xff1a; 数据库导入&#xff1a;有点粗糙&#xff0c;不知道怎么搞成两列&#xff0c;一个单词中间还是空格卧槽难搞 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <s…

Go 代码中的文档和注释

撰写清晰、简洁和全面的代码文档的指南 在软件开发领域&#xff0c;编写代码只占了一半的战斗。另一半则围绕着创建清晰、简洁和全面的文档展开&#xff0c;这些文档不仅有助于开发人员理解代码库&#xff0c;还充当未来开发的路线图。在本指南中&#xff0c;我们将深入探讨编…

Spring:通过@Lazy解决构造方法形式的循环依赖问题

一、定义2个循环依赖的类 package cn.edu.tju.domain2;import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component;Component public class A {private final B b;public B getB() {return b;}Lazypublic A(B b){this.b b;//Sy…

[React源码解析] React的设计理念和源码架构 (一)

任务分割异步执行让出执法权 文章目录 1.React的设计理念1.1 Fiber1.2 Scheduler1.3 Lane1.4 代数效应 2.React的源码架构2.1 大概图示2.2 jsx2.3 Fiber双缓存2.4 scheduler2.5 Lane模型2.6 reconciler2.7 renderer2.8 concurrent 3.React源码调试 1.React的设计理念 Fiber: 即…

python获取时间戳

使用 datetime 库获取时间。 获取当前时间&#xff1a; import datetime print(datetime.datetime.now()) . 后面的是微秒&#xff0c;也是一个时间单位&#xff0c;1秒1000000微秒。 转为时间戳&#xff1a; import datetimedate datetime.datetime.now() timestamp date…

最短路径专题5 最短路径

题目&#xff1a; 样例&#xff1a; 输入 4 5 0 2 0 1 2 0 2 5 0 3 1 1 2 2 3 2 2 输出 3 0->3->2 思路&#xff1a; 根据题目意思&#xff0c;求最短路&#xff0c;这个根据平时的Dijkstra&#xff08;堆优化&#xff09;即可&#xff0c;关键在于求路径的方法&#x…

uni-app:实现页面效果2(canvas绘制,根据页面宽度调整元素位置)

效果 代码 <template><view><!-- 车搭配指示器-双显 --><view class"content_position"><view class"content"><view class"SN"><view class"SN_title">设备1</view><view class…

视频讲解|含可再生能源的热电联供型微网经济运行优化(含确定性和源荷随机两部分代码)

1 主要内容 该视频为《含可再生能源的热电联供型微网经济运行优化》代码讲解内容&#xff0c;对应的资源下载链接为考虑源荷不确定性的热电联供微网优化-王锐matlab&#xff08;含视频讲解&#xff09;&#xff0c;对该程序进行了详尽的讲解&#xff0c;基本做到句句分析和讲解…

源码系列 之 ThreadLocal

简介 ThreadLocal的作用是做数据隔离&#xff0c;存储的变量只属于当前线程&#xff0c;相当于当前线程的局部变量&#xff0c;多线程环境下&#xff0c;不会被别的线程访问与修改。常用于存储线程私有成员变量、上下文&#xff0c;和用于同一线程&#xff0c;不同层级方法间传…

【数据结构】链表与LinkedList

作者主页&#xff1a;paper jie 的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精…

Ubuntu中启动HDFS后没有NameNode解决办法

关闭进程&#xff1a; stop-dfs.sh 格式化&#xff1a; hadoop namenode -format 出现报错信息&#xff1a; 23/10/03 22:27:04 WARN fs.FileUtil: Failed to delete file or dir [/usr/data/hadoop/tmp/dfs/name/current/fsimage_0000000000000000000.md5]: it still exi…

3种等待方式,让你学会Selenium设置自动化等待测试脚本!

一、Selenium脚本为什么要设置等待方式&#xff1f;——即他的应用背景到底是什么 应用Selenium时&#xff0c;浏览器加载过程中无法立即显示对应的页面元素从而无法进行元素操作&#xff0c;需设置一定的等待时间去等待元素的出现。&#xff08;简单来说&#xff0c;就是设置…

黑马mysql教程笔记(mysql8教程)基础篇——数据库相关概念、mysql安装及卸载、数据模型、SQL通用语法及分类(DDL、DML、DQL、DCL)

参考文章1&#xff1a;https://www.bilibili.com/video/BV1Kr4y1i7ru/ 参考文章2&#xff1a;https://dhc.pythonanywhere.com/article/public/1/ 文章目录 基础篇数据库相关概念&#xff08;数据库DataBase&#xff08;DB&#xff09;、数据库管理系统DataBase Management Sy…