学习资料

A. MMDet组件整体介绍

【博文】《轻松掌握 MMDetection 整体构建流程(一)》
系统性地介绍了MMDet中的各种组件,例如:Backbone, Neck, Head和Enhance等等。

1. 安装

1.1 新建conda环境:openmmlab

conda create --name openmmlab python=3.10 -y

1.2 使用国内镜像安装mim

pip install -U openmim -i https://pypi.tuna.tsinghua.edu.cn/simple

-U--upgrade,表示安装指定包最新可用的版本。

Note:
“这里为什么要加上“-U”这个安装选项呢?”:使用-U选项会强制升级已经安装的包,如果不使用这个选项,pip只会安装新的依赖包,而不会升级已经安装的包。在这里使用“-U”选项是为了确保已经安装的openmim包可以被升级到最新版本,以便安装MMCV和MMEngine的最新版本。

1.3 用mim安装mmcv

mim install mmcv-full

使用国内镜像进行安装

mim install mmcv-full -i https://pypi.tuna.tsinghua.edu.cn/simple

如果mim安装出现重复下载多个mmcv-full版本的问题,改用pip安装

Mmcv官方安装文档:《MMCV文档 | 安装 MMCV》

安装CPU平台的MMCV

pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cpu/torch_version/index.html

例如:安装基于torch1.12.1版本CPU平台的MMCV,

pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cpu/torch1.12.1/index.html

1.4 下载MMDet项目源代码

git clone https://github.com/open-mmlab/mmdetection.git mm-project

1.5 本地编译安装(使用清华源)

pip install -v -e . -i https://pypi.tuna.tsinghua.edu.cn/simple

如果不用本地安装的方式,可以采用“export PYTHONPATH 中加入 mmdet 的路径用法也行”,也可以试一下看看“sys.append”的方式看看行不行。

2 目录结构

configs:配置文件目录,例如:configs/centernet/centernet_resnet18_dcnv2_140e_coco.py

3 CookBook

3.1 Basics

Epoch:从1开始计数

3.2 模型训练

CPU训练:tools/train.py

示例:

python tools/train.py configs/centernet/centernet_resnet18_dcnv2_140e_coco.py

单GPU训练:tools/train.py

python tools/train.py \CONFIG_FILE \[optional arguments]

多GPU训练:tools/dist_train.sh

bash ./tools/dist_train.sh \${CONFIG_FILE} \${GPU_NUM} \[optional arguments]

CONFIG_FILE:模型配置
GPU_NUM:显卡数量

3.2 固定随机种子

固定模块参数初始化的随机种子:--seed

需要在运行train.py时,加上--seed参数,可以保证每次运行时module的初始参数相同;

Note
在论文复现时,可以查看 MMDet-repo 中给出的log文件查看模型在原始训练时使用的seed参数。

3.3 日志分析:tools/analysis_tools/analyze_logs.py

绘制参数曲线 — plot_curve

python tools/analysis_tools/analyze_logs.py plot_curve $LOG_FILE [--keys ${KEYS}] [--eval-interval ${EVALUATION_INTERVAL}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]

LOG_FILE:日志的.json文件,示例如下,

./work_dirs/config_name/yyyymmdd_hhmmss.log.json

--keys ${KEYS}:打印的参数名关键字,例如:bbox_mAP
--out ${OUT_FILE}:输出图像的文件名,例如:mAP.png
--title ${TITLE}:图像标题

Note
plot_curve会根据输出文件名,自动生成该格式的图像文件,这一点跟OpenCV的imwrite()是类似的。

使用plot_curve比较两个模型的运行指标

python tools/analysis_tools/analyze_logs.py plot_curve log1.json log2.json --keys bbox_mAP --legend run1 run2

Legend:图例

4 配置文件:model_backbone_epoch_*.py

可以使用 Config View(VSCode插件)来查看完整的配置参数;

4.1 workflow:工作流程

示例说明:当配置为workflow = [('train', n),('val', 1)],表示先进行n个epoch的训练,然后再进行1个epoch的验证,然后循环往复,如果写成 [(‘val’, 1),(‘train’, n)] 表示先进行验证,然后再开始训练。

4.2 model:模型

4.2.1 model.backbone:主干网络

(1) backbone.type:主干网络的类名

属于mmdetection/mmdet/models/backbones/__init声明的类之一;
其它参数则是传入到backbone构造函数中的形参;

(2) frozen_stages:在训练时冻结的stages

例如:ResNet 结构包括 stem + 4-stages,

  • frozen_stages=-1,表示全部可学习
  • frozen_stages=0,表示stem权重固定
  • frozen_stages=1,表示stem和第一个stage权重固定
  • frozen_stages=2,表示stem和前两个stage权重固定

自定义主干网络

关于自定义网络,可以参考mmdet的官方文档;

ResNet 主干网络class
__init__() 初始化函数
forward() 前向运算函数

3.2 data:数据读取

data.samples_per_gpu

每个GPU上载入的样本数量。

data.train:训练数据

  • train.dataset:数据集设置
  • train.dataset.type:属于mmdetection/mmdet/datasets声明的类之一,例如:CocoDataset;
  • train.dataset.ann_file:train标注.json文件;
  • train.dataset.img_prefix:训练图像文件夹;
  • train.dataset.pipeline:训练预处理流程;
  • train.dataset.filter_empty_gt:False表示不会过滤无标注图像;

Note
在默认设置下,(未使用filter_empty_gt=False的情况),在dataset读数据的时候过滤掉无标注图像的图像,使用“fliter data”的操作过滤掉这些图像样本,不参加训练。

3.3 lr_config:学习率设置

3.3.1 policy:学习率策略

step:表示StepLrUpdaterHook

在使用epoch设置训练周期的情况下,step填入的参数以epoch计数;

3.4 optimizer_config:优化器设置

Centernet_dcnv2使用了默认的优化器设置:[SGD]

优化器的类名也会与type的名称保持一致,可以在对应文件夹中查找;

3.5 checkpoint_config:检查点设置

例如:checkpoint_config = dict(interval=1)
表示每间隔1个epoch保存一次;

3.6 log_config:日志设置

log_config.interval:每间隔n次输出一次状态信息;

5 常用命令

单GPU训练:[mmdet-doc]

# CONFIG:模型配置文件
python tools/train.py ${CONFIG}

6 数据增广:data_pipeline

关于每个数据增广操作对数据结构具体的更新情况,请参考《MMDet-doc | Data loading》

6.1 Expand:搭配Resize使用实现缩放效果

关于Expand的效果图,请参考《mmdetection中数据增强的可视化 | 八、Expand》

6.2 RandomFlip:随机翻转图像

Note:
如果想要停用RandomFlip操作,不能直接在代码中注释掉,而需要将flip_ratio设成0,即flip_ratio=0.0,注意这里用的是0.0,因为函数要求flip_ratio参数是float格式。

6.3 Normalize:颜色分量归一化

对于mean&std的颜色顺序,跟to_rgb参数有关:

颜色顺序转换 mean&std颜色顺序
to_rgb=True RGB
default BGR

6.4 自定义增广操作:[mmdet-CustomizeDataPipelines]

关于自定义增广操作的示例,可以参考《数据增强神器 SimpleCopyPaste 支持全流程》

7 自定义数据集 [mmdet]

示例标注:

'images': [{'file_name': 'COCO_val2014_000000001268.jpg','height': 427,'width': 640,'id': 1268},...
],'annotations': [{'segmentation': [[192.81,247.09,...219.03,249.06]],  # if you have mask labels'area': 1035.749,'iscrowd': 0,'image_id': 1268,'bbox': [192.81, 224.8, 74.73, 33.43],'category_id': 16,'id': 42986},...
],'categories': [{'id': 0, 'name': 'car'},]

自定义数据集代码示例:

# 定义数据集字典
coco_output = {"images": [],"categories": [],"annotations": []
}
categories = [{"id": 1, "name": "..."},]

8 MMDet模块化设计原理

8.1 Config:实现字典与类属性的自动转换

MMDet知乎教程:《MMCV 核心组件分析(四): Config》

8.1.1 通过dict生成config

cfg = Config(dict(a=1, b=dict(b1=[0, 1])))# 可以通过 .属性方式访问,比较方便
cfg.b.b1 # [0, 1]

8.2 Registry:实现type字符串到模块类名的自动映射

Registry – mmcv:用于接收字典参数进行模块构造的工厂类。
关于Registry实现原理的介绍,请参考MMLab视频教程《社区开放麦#9 | OpenMMLab 模块化设计背后的功臣》
Registry机制实现了type字符串到模块类名的自动映射,而不需要手动填写字典条目,即:

self._module_dict[cls_obj.__name__] = cls_obj

8.2 Hook:实现训练过程的非侵入式功能扩展

MMDet-1.0版本的Hook点位如图所示

MMLab-2.0版本中对Hook整体结构的设计进行了一定的更新,这里我们咨询了MMDet的叶老师:

叶老师:首先 1.0-val 里的点位,在实际训练的过程中是很多是走不到的,因为验证会调用Evalhook,而在 Evalhook 里再去调用别的hook也不合适。其次2.0中,除了图示里提到的点位(train和val的点位都会起作用),还新增了一些点位,例如 before_train, after_load_checkpoint 等;
具体差异可以参考https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/migration/hook.md

Hook函数说明

IterTimerHook:用于计时的Hook类

IterTimerHook: [mmcv/runner/hooks/iter_timer.py]
IterTimerHook的计时功能最终会在EpochBasedRunnertrain()函数中调用,来输出每个iteration计算时长:

def train(self, data_loader, **kwargs):...for i, data_batch in enumerate(self.data_loader):self.data_batch = data_batchself._inner_iter = iself.call_hook('before_train_iter')self.run_iter(data_batch, train_mode=True, **kwargs)self.call_hook('after_train_iter')# iter_timer_hook.after_iter()会在after_train_iter中被调用del self.data_batchself._iter += 1self.call_hook('after_train_epoch')self._epoch += 1

8.3 训练和测试整体代码抽象流程

8.3.1 训练和验证整体流程

MMDet知乎教程:《轻松掌握 MMDetection 整体构建流程(二):训练和测试流程深入解析》

训练脚本:tools/train.py

  1. 初始化配置
    cfg = Config.fromfile(args.config)
  2. 初始化 logger
    logger = get_root_logger(log_file=log_file, log_level=cfg.log_level)
  3. 收集运行环境并且打印,方便排查硬件和软件相关问题
    env_info_dict = collect_env()
  4. 初始化 model
    model = build_detector(cfg.model, …)
  5. 初始化 datasets
    datasets = [build_dataset(cfg.data.train)]
  6. 进入训练器函数流程
    train_detector(model, datasets, cfg, distributed=distributed, …)

train_detector是训练检测器的流程函数,在文件起始处的声明为

from mmdet.apis import init_random_seed, set_random_seed, train_detector

根据import语句引用路径,跟踪到 train_detector是位于mmdet/apis/train.py文件中的函数;

MMDet核心逻辑实现: mmdet/apis/train.py

  1. 构造 data_loaders,内部会初始化 GroupSampler
    data_loaders = [build_dataloader(ds, **train_loader_cfg) for ds in dataset]
  2. 查看是否使用分布式训练,初始化对应的 DataParallel
  3. 初始化 runner,一般常用的就是EpochBasedRunner
    runner = build_runner(…)
    mmcv.build_runner(…) ⇒ runner_constructor() ⇒ runner = dict(type=‘EpochBasedRunner’, …)
  4. 注册hooks
    runner.register_training_hooks(…, custom_hooks_config=cfg.get(‘custom_hooks’, None))
  5. 如果在训练中进行val,则还需要注册eval_hook
    runner.register_hook( eval_hook(val_dataloader, **eval_cfg), priority=‘LOW’)
  6. 权重恢复和加载 [source]
  7. 运行,开始训练
    runner.run(data_loaders, cfg.workflow)
  8. Runner.run()即是EpochBasedRunner.run()
Build runner调用过程示意图

Runner训练和验证流程:EpochBasedRunner.run()

EpochBasedRunner.run()在内部会调用self.train();之后即是Model具体的训练流程,如图所示,

BaseDetector: train_step() | val_step()

其核心代码如下所示:[mmdet/models/detectors/base.py::BaseDetector]

def train_step(self, data, optimizer):# 调用本类自身的 forward 方法losses = self(**data)# 解析loss:内部包含了求和操作loss, log_vars = self._parse_losses(losses)# 返回字典对象outputs = dict(loss=loss, log_vars=log_vars, num_samples=len(data['img_metas']))return outputsdef forward(self, img, img_metas, return_loss=True, **kwargs):if return_loss:# 训练模式return self.forward_train(img, img_metas, **kwargs)else:# 测试模式return self.forward_test(img, img_metas, **kwargs)# forward_train()和forward_test()需要在子类检测器中实现,# 两个函数会在对应模式下输出结果:# forward_train()   ⇒   Loss# forward_test()    ⇒   预测结果

SubDetector: forward_train()

目前提供了两个检测器子类,TwoStageDetectorSingleStageDetector,分别用于实现 two-stage 和 single-stage 算法。
其中,CenterNet是基于SingleStageDetector实现的,所以这里我们介绍一下SingleStageDetector的前向训练过程:[mmdet/models/detectors/single_stage.py::SingleStageDetector]

def forward_train(...):super(SingleStageDetector, self).forward_train(img, img_metas)# 先经过 backbone + neck 进行特征提取x = self.extract_feat(img)# 调用 bbox_head的 forward_train()方法losses = self.bbox_head.forward_train(x, ...)return losses

8.3.2 测试流程

  1. 调用MMDataParallelMMDistributedDataParallel中的forward()方法
  2. 调用base.py中的forward()方法
  3. 调用 base.py 中的 self.forward_test 方法
  4. 如果是单尺度测试,则会调用TwoStageDetector或 SingleStageDetector 中的 simple_test方法,如果是多尺度测试,则调用 aug_test 方法
  5. 最终调用的是每个具体 Head 模块的simple_test()或者aug_test()方法(one-stage和 two-stage的head调用逻辑有些区别)

Note:在MMDet中,检测器算法解码操作的核心逻辑是在Head中实现,具体则是在simple_test()aug_test()方法中编写。

8.4 Head:模型结构+损失函数+后处理

MMDet知乎教程:《轻松掌握 MMDetection 中 Head 流程》

8.4.1 训练流程

上文说到SingleStageDetector会调用self.bbox_head.forward_train(),它是Head模块在训练流程中最外层的接口函数;
于是,我们先看看CenterNetHead类的集成关系图:

可以看到CenterNetHead是直接继承于BaseDenseHead

BaseDenseHead

BaseDenseHead基类比较简单,于是,anchor-based 和 anchor-free 算法会进一步继承派生,得到AnchorHead或者 AnchorFreeHead 类;
对于BaseDenseHead.forward_train():

def forward_train(self,x,img_metas,gt_bboxes,gt_labels=None,gt_bboxes_ignore=None,proposal_cfg=None,**kwargs):# 调用子类实现的forward()方法outs = self(x)if gt_labels is None:loss_inputs = outs + (gt_bboxes, img_metas)else:loss_inputs = outs + (gt_bboxes, gt_labels, img_metas)# 调用子类实现的loss()方法losses = self.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)if proposal_cfg is None:return losseselse:# two-stage算法还需要返回proposalsproposal_list = self.get_bboxes(*outs, img_metas=img_metas, cfg=proposal_cfg)return losses, proposal_list

子类Head一般不会重写forward_train(),但是它们都会重写forward()和loss()方法,其中:
6. forward()方法用于运行head网络部分输出分类回归分支的特征图;
7. loss()方法接收forward()输出,并且结合label计算loss值。

AnchorHead训练

Note: TTFHead直接继承于AnchorHead
对于AnchorHead,其主要是封装了anchor的生成过程。首先我们看看forward()函数的主要逻辑:

@HEADS.register_module()
class AnchorHead(BaseDenseHead, BBoxTestMixin): # BBoxTestMixin 是多尺度测试时候调用def forward(self, feats):# 对每张特征图单独计算预测输出return multi_apply(self.forward_single, feats)# 单尺度分支的分类回归输出def forward_single(self, x):cls_score = self.conv_cls(x)bbox_pred = self.conv_reg(x)return cls_score, bbox_pred
Loss计算的时序关系图


[mmdetv2-view/pumls/head_scope.puml]

8.4.2 测试流程

前面说过在测试流程中,最终会调用 Head 模块的 simple_test()aug_test()方法分别进行单尺度或多尺度测试,涉及到具体代码层面,one-stage 和 two-stage 的函数调用有区别,但最终调用的依然是Head模块的get_bboxes()方法;

AnchorHead测试

One-stage检测器在单尺度模式下,会直接调用self.bbox_head.get_bboxes()方法;
其中,基于AnchorHead实现的调用流程是:

  1. 遍历每个特征尺度输出分支,利用nms_pre配置参数对该层预测结果按照scores值进行从大到小进行topk截取,保留scores最高的前nms_pre的预测结果;
  2. 对保留的预测结果进行bbox解码还原操作;
  3. 还原到最原始图片尺度;
  4. 如果需要进行nms,则对所有分支预测保留结果进行统一nms即可,否则直接属于多尺度预测结果。

多尺度测试

除了RPN算法的多尺度测试是在mmdet/models/dense_heads/rpn_test_mixin.py中实现外,
其余 Head 多尺度测试都是在mmdet/models/dense_heads/dense_test_mixins.py::BBoxTestMixin中实现,其思路是对多尺度图片中每张图片单独运行get_bboxes(),然后还原到原图尺度,最后把多尺度图片预测结果合并统一进行nms。

BBox编解码

关于MMDet中RetinaNet的编解码操作,请参考《轻松掌握 MMDetection 中常用算法(一):RetinaNet 及配置详解》

9 函数和类说明

9.1 mmdet.apis:mmdet的核心函数

apis.train_detector():训练过程主要函数

train_detector()内部会调用runner.run()来执行主要的训练过程。

参数说明:

  • model:检测器。
  • dataset:数据集对象。

变量说明:

  • cfg:【Config】配置信息对象。
Cfg:配置信息
Cfg.runner:运行信息字典

9.2 mmdet\models:模型库

BACKBONES:主干网络注册器

用于主干网络的注册,即:@BACKBONES.register_module()

9.3 mmdet.datasets:数据集读取处理

9.3.1 CustomDataset:自定义数据集

prepare_train_img():读取训练图像

9.4 mmdet.runner:运行脚本库

Runner.EpochBasedRunner

[EpochBasedRunner from mmcv/runner/epoch_based_runner.py]

run():进行一次workflow指定的训练过程

变量说明:

  • epoch_runner:【EpochBasedRunner.Method】对应self.train() | self.val()函数。

9.3 Head:CenterNetHead


继承关系:nn.Module ⇒ mmcv.*.BaseModule ⇒ mmdet.*.BaseDenseHead ⇒ CenterNetHead

10 自定义Hook

官方教程:How to implement a custom hook @ MMDetection documentation

11 提交PR

请参考博文《【MMDet】提交PR的学习笔记》

12 MMDet-docs调试笔记

MMDet-docs在调试时,其python环境是可以单独配置的;
关于配置MM-docs编译环境的教程,请参考[MMEngine | Build Documentation]
不过在调试过程中,遇到了编译不成功的问题,

正在运行 Sphinx v4.0.2
创建输出目录… 完成
myst v0.18.1: MdParserConfig(commonmark_only=False, gfm_only=False, enable_extensions=[‘colon_fence’], disable_syntax=[], all_links_external=False, url_schemes=(‘http’, ‘https’, ‘mailto’, ‘ftp’), ref_domains=None, highlight_code_blocks=True, number_code_blocks=[], title_to_header=False, heading_anchors=3, heading_slug_func=None, footnote_transition=True, words_per_minute=200, sub_delimiters=(‘{’, ‘}’), linkify_fuzzy_links=True, dmath_allow_labels=True, dmath_allow_space=True, dmath_allow_digits=True, dmath_double_inline=False, update_mathjax=True, mathjax_classes=‘tex2jax_process|mathjax_process|math|output_area’)
Traceback (most recent call last):
File “/home/***/mmdetection/docs/en/./stat.py”, line 31, in
assert len(_papertype) > 0
AssertionError
构建 [mo]: 0 个 po 文件的目标文件已过期
构建 [html]: 29 个源文件的目标文件已过期
更新环境: [新配置] 已添加 29,0 已更改,0 已移除
阅读源… [ 3%] 1_exist_data_model
Extension error (sphinx_markdown_tables):
Handler <function process_tables at 0x7f3e3a519670> for event ‘source-read’ threw an exception (exception: init() takes 2 positional arguments but 3 were given)
make: *** [Makefile:20:html] 错误 2

我们已经在 MMDet-Github-Discussions上提出了问题“Docs making error: mmdet/docs/en/./stat.py", line 31, in assert len(_papertype) > 0 AssertionError #9029”,不过目前还没有收到回复;

13 MMYOLO模型结构图

请参考博文《【MMDetection】MMYOLO模型结构图的学习笔记》

14 学习笔记

14.1 MMDet中有的import语句引入多个关键字时加了“()”,例如:“from … import (DistSamplerSeedHook, EpochBasedRunner, …)”,这是什么特殊的用法吗?

关于import导入时加入小括号的作用,请参考博文《python导入时小括号大作用》
简单来说,其原因是:有时需要导入的函数比较多,会超过一行80个字符的PEP建议。于是PEP建议使用标准分组机制,也就是用小括号括起来,例如: from os import (name,getcwd)这种方式,就可以分成多行了。

MMDetection的学习笔记相关推荐

  1. Detectron2和MMDetection的学习笔记

    1 关于选择哪个框架比较好 我会选择Detectron2: 因为MMDetection的Metrics的代码明显是有问题的, 在GitHub上面的issue的链接如下: https://github. ...

  2. MMDetection学习笔记(一):训练与测试

    MMDetection学习笔记(一):训练与测试 MMDetection介绍 Config配置文件 配置文件结构 配置文件命名 配置文件示例 自定义COCO格式数据 MMDetection使用 训练 ...

  3. EfficientDet(EfficientNet+BiFPN)论文超详细解读(翻译+学习笔记+代码实现)

    前言 在之前我们介绍过EfficientNet(直通车:[轻量化网络系列(6)]EfficientNetV1论文超详细解读(翻译 +学习笔记+代码实现) [轻量化网络系列(7)]EfficientNe ...

  4. MMAction2 学习笔记 (一)——骨骼动作识别模型相关内容

    MMAction2 学习笔记 (一)--骨骼动作识别模型相关内容 0- 写在前面 好久不用CSDN发东西了,近期研究可以说有进展却又没什么进展,达到方向切换到了动作识别,目前正在实习,具体的内容方向是 ...

  5. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  6. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  7. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  8. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  9. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

最新文章

  1. 人工智能阴影检测与去除,实现一种基于反射的阴影检测与去除方法
  2. 支持批任务的Coscheduling/Gang scheduling
  3. 蔬菜大棚成本_蔬菜大棚建设标准和成本
  4. vmware workstation虚拟环境安装及创建虚拟机
  5. 计算机组成原理上机试卷,计算机组成原理试卷及答案
  6. 【FastDFS-V5.11】Linux下FastDFS+Nginx实现分布式图片服务器搭建详细教程(单机模式)
  7. 【方法篇】质谱手段分析组蛋白修饰类型
  8. autoit脚本实现电脑加域,退域,重加域
  9. 华为设备配置VRRP,实现设备网关冗余备份
  10. [推荐] 6410 休眠唤醒实现小结 [问题点数:40分]【转】
  11. Linux 下屏幕旋转
  12. k8s学习笔记2-搭建harbor私有仓库
  13. H5页面制作功能真的很强大!
  14. mysql commit work_数据库commit work
  15. 商品3D展示来啦,HMS Core3D建模服务助力电商发展
  16. 大哥大佬们这个怎么改成 成功
  17. scrapy爬虫实战教程
  18. OBJECTS IN SEMANTIC TOPOLOGY
  19. 使用备份软件快速备份VMware虚拟机
  20. Java内功设计模式 part2

热门文章

  1. 北邮网安382分(408 133分)经验贴
  2. [django]Django外键(ForeignKey)操作以及related_name的作用
  3. upic上传GitHub图床失败解决
  4. 基于VUE的ElementUi可视化表单设计器布局器
  5. 典型相关分析(CCA)相关资料
  6. 区块链扩张路径变局:从技术比拼转向生态落地
  7. shell小技巧(十五)模拟抓阄
  8. 【git push报错】:See the ‘Note about fast-forwards‘ in ‘git push --help‘ for details
  9. 朋友圈那些卖网课的资源都是哪来的?靠谱吗?
  10. 在Ubuntu上安装搜狗拼音输入法出错的解决办法