YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)
YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)
- 前言
- YOLOV8
- nn文件夹
- modules.py
- task.py
- models文件夹
- 总结
前言
因为毕设用到了YOLO,鉴于最近V8刚出,因此考虑将注意力机制加入到v8中。
YOLOV8
代码地址:YOLOV8官方代码
使用pip安装或者clone到本地,在此不多赘述了。下面以使用pip安装ultralytics包为例介绍。
进入ultralytics文件夹
nn文件夹
再进入nn文件夹。
-- modules.py:在里面存放着各种常用的模块,如:Conv,DWConv,ConvTranspose,TransformerLayer,Bottleneck等
-- tasks.py: 在里面导入了modules中的基本模块组建model,根据不同的下游任务组建不同的model。
modules.py
在该文件中,我们可以写入自己的注意力模块,或者使用V8已经提供的CBAM模块(见代码的CBAM类)
"""
通道注意力模型: 通道维度不变,压缩空间维度。该模块关注输入图片中有意义的信息。
1)假设输入的数据大小是(b,c,w,h)
2)通过自适应平均池化使得输出的大小变为(b,c,1,1)
3)通过2d卷积和sigmod激活函数后,大小是(b,c,1,1)
4)将上一步输出的结果和输入的数据相乘,输出数据大小是(b,c,w,h)。
"""
class ChannelAttention(nn.Module):# Channel-attention module https://github.com/open-mmlab/mmdetection/tree/v3.0.0rc1/configs/rtmdetdef __init__(self, channels: int) -> None:super().__init__()self.pool = nn.AdaptiveAvgPool2d(1)self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True)self.act = nn.Sigmoid()def forward(self, x: torch.Tensor) -> torch.Tensor:return x * self.act(self.fc(self.pool(x)))"""
空间注意力模块:空间维度不变,压缩通道维度。该模块关注的是目标的位置信息。
1) 假设输入的数据x是(b,c,w,h),并进行两路处理。
2)其中一路在通道维度上进行求平均值,得到的大小是(b,1,w,h);另外一路也在通道维度上进行求最大值,得到的大小是(b,1,w,h)。
3) 然后对上述步骤的两路输出进行连接,输出的大小是(b,2,w,h)
4)经过一个二维卷积网络,把输出通道变为1,输出大小是(b,1,w,h)
4)将上一步输出的结果和输入的数据x相乘,最终输出数据大小是(b,c,w,h)。
"""
class SpatialAttention(nn.Module):# Spatial-attention moduledef __init__(self, kernel_size=7):super().__init__()assert kernel_size in (3, 7), 'kernel size must be 3 or 7'padding = 3 if kernel_size == 7 else 1self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)self.act = nn.Sigmoid()def forward(self, x):return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))class CBAM(nn.Module):# Convolutional Block Attention Moduledef __init__(self, c1, kernel_size=7): # ch_in, kernelssuper().__init__()self.channel_attention = ChannelAttention(c1)self.spatial_attention = SpatialAttention(kernel_size)def forward(self, x):return self.spatial_attention(self.channel_attention(x))
如果使用V8的CBAM模块,则不需要更改modules.py的内容。如果使用自己的注意力模块,只需要在该文件后面添加对应的代码即可。
task.py
在该文件中,通过import modules.py文件中的模块来构建模型。
在文件开头导入需要的模块,可以看到modules中的很多模块在v8中并没有用到。我们在最后添加对应的CBAM模块。
from ultralytics.nn.modules import (C1, C2, C3, C3TR, SPP, SPPF, Bottleneck, BottleneckCSP, C2f, C3Ghost, C3x, Classify,Concat, Conv, ConvTranspose, Detect, DWConv, DWConvTranspose2d, Ensemble, Focus,GhostBottleneck, GhostConv, Segment, CBAM)
之后修改对应的parse_model
方法(对应428行)
添加分支elif m is CBAM:
,具体代码如下:
def parse_model(d, ch, verbose=True): # model_dict, input_channels(3)# Parse a YOLO model.yaml dictionaryif verbose:LOGGER.info(f"\n{'':>3}{'from':>20}{'n':>3}{'params':>10} {'module':<45}{'arguments':<30}")nc, gd, gw, act = d['nc'], d['depth_multiple'], d['width_multiple'], d.get('activation')if act:Conv.default_act = eval(act) # redefine default activation, i.e. Conv.default_act = nn.SiLU()if verbose:LOGGER.info(f"{colorstr('activation:')} {act}") # printch = [ch]layers, save, c2 = [], [], ch[-1] # layers, savelist, ch outfor i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, argsm = eval(m) if isinstance(m, str) else m # eval stringsfor j, a in enumerate(args):# TODO: re-implement with eval() removal if possible# args[j] = (locals()[a] if a in locals() else ast.literal_eval(a)) if isinstance(a, str) else awith contextlib.suppress(NameError):args[j] = eval(a) if isinstance(a, str) else a # eval stringsn = n_ = max(round(n * gd), 1) if n > 1 else n # depth gainif m in (Classify, Conv, ConvTranspose, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, Focus,BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x):c1, c2 = ch[f], args[0]if c2 != nc: # if c2 not equal to number of classes (i.e. for Classify() output)c2 = make_divisible(c2 * gw, 8)args = [c1, c2, *args[1:]]if m in (BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, C3x):args.insert(2, n) # number of repeatsn = 1elif m is nn.BatchNorm2d:args = [ch[f]]elif m is Concat:c2 = sum(ch[x] for x in f)elif m in (Detect, Segment):args.append([ch[x] for x in f])if m is Segment:args[2] = make_divisible(args[2] * gw, 8)elif m is CBAM:"""ch[f]:上一层的args[0]:第0个参数c1:输入通道数c2:输出通道数"""c1, c2 = ch[f], args[0]# print("ch[f]:",ch[f])# print("args[0]:",args[0])# print("args:",args)# print("c1:",c1)# print("c2:",c2)if c2 != nc: # if c2 not equal to number of classes (i.e. for Classify() output)c2 = make_divisible(c2 * gw, 8)args = [c1,*args[1:]]else:c2 = ch[f]m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # modulet = str(m)[8:-2].replace('__main__.', '') # module typem.np = sum(x.numel() for x in m_.parameters()) # number paramsm_.i, m_.f, m_.type = i, f, t # attach index, 'from' index, typeif verbose:LOGGER.info(f'{i:>3}{str(f):>20}{n_:>3}{m.np:10.0f} {t:<45}{str(args):<30}') # printsave.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelistlayers.append(m_)if i == 0:ch = []ch.append(c2)return nn.Sequential(*layers), sorted(save)
注意传入的参数为上一层输出,要注意CBAM模块的参数和传入参数的对应。读者可以自行print比较。
models文件夹
返回上一级目录,进入models文件夹。
可以看到该文件夹中还有v5、v3对应的模型配置文件,所以也可以使用该包进行v5和v3的训练。
进入v8文件夹
打开对应的yolov8.yaml,如下所示。该文件是V8对应的配置文件,里面包括了类别数,模型大小(n,s,m,l,x),backbone和head。
# Ultralytics YOLO 🚀, GPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'# [depth, width, max_channels]n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPss: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPsm: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPsl: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPsx: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs# YOLOv8.0n backbone
backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4- [-1, 3, C2f, [128, True]]- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8- [-1, 6, C2f, [256, True]]- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16- [-1, 6, C2f, [512, True]]- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32- [-1, 3, C2f, [1024, True]]- [-1, 1, SPPF, [1024, 5]] # 9# YOLOv8.0n head
head:- [-1, 1, nn.Upsample, [None, 2, 'nearest']]- [[-1, 6], 1, Concat, [1]] # cat backbone P4- [-1, 3, C2f, [512]] # 12- [-1, 1, nn.Upsample, [None, 2, 'nearest']]- [[-1, 4], 1, Concat, [1]] # cat backbone P3- [-1, 3, C2f, [256]] # 15 (P3/8-small)- [-1, 1, Conv, [256, 3, 2]]- [[-1, 12], 1, Concat, [1]] # cat head P4- [-1, 3, C2f, [512]] # 18 (P4/16-medium)- [-1, 1, Conv, [512, 3, 2]]- [[-1, 9], 1, Concat, [1]] # cat head P5- [-1, 3, C2f, [1024]] # 21 (P5/32-large)- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)
我们复制一份,以yolov8x为例,并改名为myyolo.yaml
# Ultralytics YOLO 🚀, GPL-3.0 license# Parameters
nc: 80 # number of classes
depth_multiple: 1.00 # scales module repeats
width_multiple: 1.25 # scales convolution channels# YOLOv8.0x backbone
backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4- [-1, 3, C2f, [128, True]]- [-1, 3, CBAM, [128,7]]- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8- [-1, 6, C2f, [256, True]]- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16- [-1, 6, C2f, [512, True]]- [-1, 1, Conv, [512, 3, 2]] # 7-P5/32- [-1, 3, C2f, [512, True]]- [-1, 1, SPPF, [512, 5]] # 9- [-1, 3, CBAM, [512,7]]# YOLOv8.0x head
head:- [-1, 1, nn.Upsample, [None, 2, 'nearest']]- [[-1, 6], 1, Concat, [1]] # cat backbone P4- [-1, 3, C2f, [512]] # 12- [-1, 1, nn.Upsample, [None, 2, 'nearest']]- [[-1, 4], 1, Concat, [1]] # cat backbone P3- [-1, 3, C2f, [256]] # 15 (P3/8-small)- [-1, 1, Conv, [256, 3, 2]]- [[-1, 12], 1, Concat, [1]] # cat head P4- [-1, 3, C2f, [512]] # 18 (P4/16-medium)- [-1, 1, Conv, [512, 3, 2]]- [[-1, 9], 1, Concat, [1]] # cat head P5- [-1, 3, C2f, [512]] # 21 (P5/32-large)- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)
我们在SPPF模块后添加一层CBAM模块,参数为[512,7],7为SpatialAttention
对应的卷积核大小,值可为3或7,其他会报错。
添加完后使用对应的yaml配置文件训练即可。
yolo task=detect mode=train model=myyolo.yaml data=datasets/data/MOT20Det/VOC2007/mot20.yaml batch=32 epochs=80 imgsz=640 workers=16 device=\'0,1,2,3\'
值得注意的是,如果添加了多层CBAM模块,可能会导致各个模块对应的层数改变,因此需要同时修改head中各个layer from对应的层数。
初始YOLOV8X默认的层数如下
# 默认
# 0 -1 1 2320 ultralytics.nn.modules.Conv [3, 80, 3, 2]
# 1 -1 1 115520 ultralytics.nn.modules.Conv [80, 160, 3, 2]
# 2 -1 3 436800 ultralytics.nn.modules.C2f [160, 160, 3, True]
# 3 -1 1 461440 ultralytics.nn.modules.Conv [160, 320, 3, 2]
# 4 -1 6 3281920 ultralytics.nn.modules.C2f [320, 320, 6, True]
# 5 -1 1 1844480 ultralytics.nn.modules.Conv [320, 640, 3, 2]
# 6 -1 6 13117440 ultralytics.nn.modules.C2f [640, 640, 6, True]
# 7 -1 1 3687680 ultralytics.nn.modules.Conv [640, 640, 3, 2]
# 8 -1 3 6969600 ultralytics.nn.modules.C2f [640, 640, 3, True]
# 9 -1 1 1025920 ultralytics.nn.modules.SPPF [640, 640, 5]
# 10 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
# 11 [-1, 6] 1 0 ultralytics.nn.modules.Concat [1]
# 12 -1 3 7379200 ultralytics.nn.modules.C2f [1280, 640, 3]
# 13 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
# 14 [-1, 4] 1 0 ultralytics.nn.modules.Concat [1]
# 15 -1 3 1948800 ultralytics.nn.modules.C2f [960, 320, 3]
# 16 -1 1 922240 ultralytics.nn.modules.Conv [320, 320, 3, 2]
# 17 [-1, 12] 1 0 ultralytics.nn.modules.Concat [1]
# 18 -1 3 7174400 ultralytics.nn.modules.C2f [960, 640, 3]
# 19 -1 1 3687680 ultralytics.nn.modules.Conv [640, 640, 3, 2]
# 20 [-1, 9] 1 0 ultralytics.nn.modules.Concat [1]
# 21 -1 3 7379200 ultralytics.nn.modules.C2f [1280, 640, 3]
# 22 [15, 18, 21] 1 8795008 ultralytics.nn.modules.Detect [80, [320, 640, 640]]
增加对应的模块后,之后的层数的layer+1,因此需要适当更改,不然会报concat维度不匹配的错误,如下
RuntimeError: Sizes of tensors must match except in dimension 1. Expected size 16 but got size 32 for tensor number 1 in the list.
总结
添加注意力模块只需要3步
1、在对应的modules.py中添加需要的模块
2、在task.py中引入modules.py中的模块,并进行适当的参数匹配
3、修改对应的models文件夹中的yaml文件,并注意层数问题。
之后就可以进行正常训练了
相关文章:

YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)
YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)前言YOLOV8nn文件夹modules.pytask.pymodels文件夹总结前言 因为毕设用到了YOLO,鉴于最近V8刚出,因此考虑将注意力机制加入到v8中。 YOLOV8 代码地址&am…...

Spark Streaming DStream的操作
一、DStream的定义 DStream是离散流,Spark Streaming提供的一种高级抽象,代表了一个持续不断的数据流。DStream可以通过输入数据源来创建,比如Kafka、Flume,也可以通过对其他DStream应用高阶函数来创建,比如map、redu…...

蓝桥杯冲刺 - week1
文章目录💬前言🌲day192. 递归实现指数型枚举843. n-皇后问题🌲day2日志统计1209. 带分数🌲day3844. 走迷宫1101. 献给阿尔吉侬的花束🌲day41113. 红与黑🌲day51236. 递增三元组🌲day63491. 完全…...

Leetcode27. 移除元素
目录一、题目描述:二、解决思路和代码1. 解决思路2. 代码一、题目描述: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用…...

ViewService——一种保证客户端与服务端同步的方法
简介在分布式系统中,最常见的场景就是主备架构。但是如果主机不幸宕机,如何正确的通知客户端当前后端服务器的状况成为一个值得研究的问题。本文描述了一种简单的模型用于解决此问题。背景以一个分布式的Key-Value数据库为背景。数据库对外提供3个接口Ge…...

使用STM32F103ZE开发贪吃蛇游戏
目录 前言 一、设置FreeROTS用户任务 (1)事件event任务 (2)按键输入方向控制任务 (3)果实食物任务 (4)显示任务函数 (3)开始任务 二、主函数 三、ADC采样…...

如何利用Web3D技术打造在线虚拟展览馆
随着Web3D技术的不断发展,越来越多的企业和组织开始将其应用于虚拟展览馆的建设中。虚拟展览馆可以为观众提供高度沉浸式的展览体验,让观众可以随时随地参观各种展览,同时也为展览组织者提供了更多的展示方式和机会。下面将介绍如何利用Web3D…...

第二十三章 opengl之高级OpenGL(实例化)
OpenGL实例化实例化数组绘制小行星带实例化 综合应用。 如果绘制了很多的模型,但是大部分的模型包含同一组顶点数据,只是不同的世界空间变换。 举例:一个全是草的场景,每根草都是一个包含了几个小三角形的模型。需要绘制很多根草…...

C++ String类总结
头文件 #include <string>构造函数 default (1) basic_string();explicit basic_string (const allocator_type& alloc); copy (2) basic_string (const basic_string& str);basic_string (const basic_string& str, const allocator_type& alloc); su…...

内网升级“高效安全”利器!统信软件发布私有化更新管理平台
随着数字化的深度推进,信息安全重要性进一步凸显。建设自主可控的国产操作系统,提升信息安全自主能力,已成为国家重要战略之一。 操作系统安全对计算机系统的整体安全发挥着关键作用,各类客户往往需要在第一时间获取更新与安全补…...

JAVA开发(自研项目的开发与推广)
https://live.csdn.net/v/284629 案例背景: 作为JAVA开发人员,我们可以开发无数多的web项目,电商系统,小程序,H5商城。有时候作为技术研发负责人,项目做成了有时候也需要对内进行内测,对外进行…...

Mysql用户权限分配详解
文章目录MySQL 权限介绍一、Mysql权限级别分析(1)全局级别(1.1) USER表的组成结构(1.1.1) 用户列(1.1.2) 权限列(1.1.3) 安全列(1.1.4)…...

【TypeScript 入门】13.枚举类型
枚举类型 枚举类型:定义包含被命名的常量的集合。比如 TypeScript 支持枚举数字、字符两种常量值类型。 使用方式: enum + 枚举名字 + 花括弧包裹被命名了的常量成员: enum Size {S,M,L } const a = Size.M console.log(Size, Size)...

Python科学计算:偏微分方程1
首先,我们来看初边值问题:伯格斯方程:假设函数是定义在上的函数,且满足:右侧第一项表示自对流,第二项则表示扩散,在许多物理过程中,这两种效应占据着主导地位,为了固定一…...

PLS-DA分类的实现(基于sklearn)
目录 简单介绍 代码实现 数据集划分 选择因子个数 模型训练并分类 调用函数 简单介绍 (此处取自各处资料) PLS-DA既可以用来分类,也可以用来降维,与PCA不同的是,PCA是无监督的,PLS-DA是有监督的…...

常用hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。理解:hook是react提供的函数API官方提供的hook基础hookuseState APIconst [state, setState] useState(initialState); //返回state值 以及更新state的方法 …...

TryHackMe-GoldenEye(boot2root)
GoldenEye 这个房间将是一个有指导的挑战,以破解詹姆斯邦德风格的盒子并获得根。 端口扫描 循例nmap Web枚举 进入80 查看terminal.js 拿去cyberchef解码 拿着这组凭据到/sev-home登录 高清星际大战 POP3枚举 使用刚刚的凭据尝试登录pop3 使用hydra尝试爆破 这…...

Elasticsearch基本安全加上安全的 HTTPS 流量
基本安全加上安全的 HTTPS 流量 在生产环境中,除非您在 HTTP 层启用 TLS,否则某些 Elasticsearch 功能(例如令牌和 API 密钥)将被禁用。这个额外的安全层确保进出集群的所有通信都是安全的。 当您在模式下运行该elasticsearch-ce…...

C语言-程序环境和预处理(2)
文章目录预处理详解1.预定义符号2.#define2.1#define定义的标识符2.2#define定义宏2.3#define替换规则注意事项:2.4#和###的作用##的作用2.5带副作用的宏参数2.6宏和函数的对比宏的优势:宏的劣势:宏和函数的一个对比命名约定3.undef4.条件编译…...

JVM 收集算法 垃圾收集器 元空间 引用
文章目录JVM 收集算法标记-清除算法标记-复制算法标记-整理算法JVM垃圾收集器Serial收集器ParNew收集器Parallel Scavenge /Parallel Old收集器CMS收集器Garbage First(G1)收集器元空间引用强引用软引用弱引用虚引用JVM 收集算法 前面我们了解了整个堆内存实际是以分代收集机制…...

clip精读
开头部分 1. 要点一 从文章题目来看-目的是:使用文本监督得到一个可以迁移的 视觉系统 2.要点二 之前是 fix-ed 的class 有诸多局限性,所以现在用大量不是精细标注的数据来学将更好,利用的语言多样性。——这个方法在 nlp其实广泛的存在&…...

vue 首次加载慢优化
目前使用的是vue2版本 1.路由懒加载(实现按需加载) component: resolve > require([/views/physicalDetail/index], resolve)2.gzip压缩插件(需要运维nginx配合) 第一步,下载compression-webpack-plugin cnpm i c…...

WuThreat身份安全云-TVD每日漏洞情报-2023-03-21
漏洞名称:CairoSVG 文件服务器端请求伪造 漏洞级别:严重 漏洞编号:CVE-2023-27586 相关涉及:CairoSVG 在 2.7.0 版本之前 漏洞状态:POC 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2023-06718 漏洞名称:WP Meta SEO WordPress 授权不当导致任意重定向 漏洞级…...

【Android -- 开发工具】Xshell 6 安装和使用教程
一、简介 Xshell 其实就是一个远程终端工具,它可以将你的个人电脑和你在远端的机器连接起来,通过向 Xshell 输入命令然后他通过网络将命令传送给远端Linux机器然后远端的Linux机器将其运行结果通过网络传回个人电脑。 二、Xshell 6 的安装 首先&#…...

国民技术RTC备份寄存器RTC_BKP
根据手册资料知道RTC_BKP的地址,代码如下 #include "main.h" #include "usart.h"void USART2_Configuration(void) {USART_InitType USART_InitStructure;GPIO_InitType GPIO_InitStructure;GPIO_InitStruct(&GPIO_InitStructure);RCC_Ena…...

resnet网络特征提取过程可视化
我们在训练图片时,是不是要看看具体提取时的每个特征图提取的样子,找了很多,终于功夫不负有心人,找到了,通过修改的代码: resnet代码: import torch import torch.nn as nn from torchvision…...

FPGA打砖块游戏设计(有上板照片)VHDL
这是一款经典打砖块游戏,我们的努力让它更精致更好玩,我们将它取名为打砖块游戏(Flyball),以下是该系统的一些基本功能: 画面简约而经典,色彩绚丽而活泼,动画流畅 玩家顺序挑战3个不同难度的级别,趣味十足 计分功能,卡通字母数字 4条生命值,由生命条显示…...

【Unity入门】3D物体
【Unity入门】3D物体 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (一)物体移动旋转缩放 (1)物体移动 在上一篇文章【Unity入门】场景视图操作我们学会了在场景中创建3…...

网络现代化势在必行,VMware 发布软件定义网络 SD-WAN 全新方案
出品 | CSDN云计算 作为计算存储网络基础设施三大件之一,网络一直是 IT 核心技术,并不断向前发展。 数字化转型浪潮下,各行业都在探索创新应用,而数字化创新,也是对 5G 和云边端等网络基础设施提出更高需求,…...

java学习笔记——抽象类
2.1 概述 由来 父类中的方法,被他的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有主体的方法称为抽象方法。java语法规定,包含抽象…...