DAMO-YOLO训练自己的数据集,使用onnxruntime推理部署

DAMO-YOLO训练自己的数据集,使用onnxruntime推理部署

DAMO-YOLO 是阿里达摩院智能计算实验室开发的一种兼顾速度与精度的目标检测算法,在高精度的同时,保持了很高的推理速度。

DAMO-YOLO 是在 YOLO 框架基础上引入了一系列新技术,对整个检测框架进行了大幅的修改。具体包括:

  1. 基于 NAS 搜索的新检测 backbone 结构,利用 MAE-NAS 方法快速搜索出适合检测任务的网络结构,比如MAE-NAS-L35 和 MAE-NAS-L45。
  2. 更深的 neck 结构,采用 RepGFPN技术,实现了高效的多尺度特征融合,提升了特征表达能力和模型性能。 精简的 head 结构,采用 ZeroHead技术,减少了冗余的参数和计算量,提高了模型速度和精度。
  3. 引入蒸馏技术实现效果的进一步提升,利用大模型作为教师模型,小模型作为学生模型,通过知识蒸馏方法提高小模型的泛化能力。
  4. DAMO-YOLO还提供高效的训练策略和便捷易用的部署工具,能够快速解决工业落地中的实际问题。可以通过以下链接访问 DAMO-YOLO的代码和文档,或者通过以下链接访问 DAMO-YOLO 在 ModelScope 平台上的在线体验。

1.modelscope平台:https://modelscope.cn/models/damo/cv_tinynas_objectdetection_damoyolo/summary
2.github链接:https://github.com/tinyvision/DAMO-YOLO
在这里插入图片描述

1.数据准备

DAMO-YOLO目前支持coco数据集voc数据集格式,本文推荐使用coco数据集格式,如果您是用labelimg标注的xml文件,可以使用下述voc2coco.py脚本进行一键转换(需要按我的格式摆放数据,否则的话需要改脚本中的路径):

.
├── ./voc2coco.py
├── ./data
│   ├── ./data/Annotations
│   │   ├── ./data/Annotations/0.xml
│   │   ├── ./data/Annotations/1000.xml
│   │   ├── ...
│   ├── ./data/images
│   │   ├── ./data/images/0.jpg
│   │   ├── ./data/images/1000.jpg
│   │   ├── ...

voc2coco.py脚本,注意需要改下category_set类别(换成自己数据集的):

import xml.etree.ElementTree as ET
import os
import json
import collections
import random
import shutilcategory_set = ['ship']# 设置随机数种子,可以是任意整数
random_seed = 42# 设置随机数种子
random.seed(random_seed)coco_train = dict()
coco_train['images'] = []
coco_train['type'] = 'instances'
coco_train['annotations'] = []
coco_train['categories'] = []coco_val = dict()
coco_val['images'] = []
coco_val['type'] = 'instances'
coco_val['annotations'] = []
coco_val['categories'] = []# category_set = dict()
image_set = set()
train_image_id = 1
val_image_id = 200000  # Assuming you have less than 200000 images
category_item_id = 1
annotation_id = 1def split_list_by_ratio(input_list, ratio=0.8):# 计算切分的索引位置split_index = int(len(input_list) * ratio)# 随机打乱列表random.shuffle(input_list)# 划分为两个列表并返回return input_list[:split_index], input_list[split_index:]def addCatItem(name):'''增加json格式中的categories部分'''global category_item_idcategory_item = collections.OrderedDict()category_item['supercategory'] = 'none'category_item['id'] = category_item_idcategory_item['name'] = namecoco_train['categories'].append(category_item)coco_val['categories'].append(category_item)category_item_id += 1def addImgItem(file_name, size, img_suffixes, is_train):global train_image_id  # 声明变量为全局变量global val_image_id  # 声明变量为全局变量# global image_idif file_name is None:raise Exception('Could not find filename tag in xml file.')if size['width'] is None:raise Exception('Could not find width tag in xml file.')if size['height'] is None:raise Exception('Could not find height tag in xml file.')# image_item = dict()    #按照一定的顺序,这里采用collections.OrderedDict()image_item = collections.OrderedDict()jpg_name = os.path.splitext(file_name)[0] + img_suffixesimage_item['file_name'] = jpg_nameimage_item['width'] = size['width']image_item['height'] = size['height']# image_item['id'] = image_id# coco['images'].append(image_item)if is_train:image_item['id'] = train_image_idcoco_train['images'].append(image_item)image_id = train_image_idtrain_image_id += 1else:image_item['id'] = val_image_idcoco_val['images'].append(image_item)image_id = val_image_idval_image_id += 1image_set.add(jpg_name)image_id = image_id + 1return image_iddef addAnnoItem(object_name, image_id, category_id, bbox, is_train):global annotation_id# annotation_item = dict()annotation_item = collections.OrderedDict()annotation_item['segmentation'] = []seg = []# bbox[] is x,y,w,h# left_topseg.append(bbox[0])seg.append(bbox[1])# left_bottomseg.append(bbox[0])seg.append(bbox[1] + bbox[3])# right_bottomseg.append(bbox[0] + bbox[2])seg.append(bbox[1] + bbox[3])# right_topseg.append(bbox[0] + bbox[2])seg.append(bbox[1])annotation_item['segmentation'].append(seg)annotation_item['area'] = bbox[2] * bbox[3]annotation_item['iscrowd'] = 0annotation_item['image_id'] = image_idannotation_item['bbox'] = bboxannotation_item['category_id'] = category_idannotation_item['id'] = annotation_idannotation_item['ignore'] = 0annotation_id += 1# coco['annotations'].append(annotation_item)if is_train:coco_train['annotations'].append(annotation_item)else:coco_val['annotations'].append(annotation_item)def parseXmlFiles(xml_path, xmllist, img_suffixes, is_train):for f in xmllist:if not f.endswith('.xml'):continuebndbox = dict()size = dict()current_image_id = Nonecurrent_category_id = Nonefile_name = Nonesize['width'] = Nonesize['height'] = Nonesize['depth'] = Nonexml_file = os.path.join(xml_path, f)print(xml_file)tree = ET.parse(xml_file)root = tree.getroot()  # 抓根结点元素if root.tag != 'annotation':  # 根节点标签raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag))# elem is <folder>, <filename>, <size>, <object>for elem in root:current_parent = elem.tagcurrent_sub = Noneobject_name = None# elem.tag, elem.attrib,elem.textif elem.tag == 'folder':continueif elem.tag == 'filename':file_name = elem.textif file_name in category_set:raise Exception('file_name duplicated')# add img item only after parse <size> tagelif current_image_id is None and file_name is not None and size['width'] is not None:if file_name not in image_set:current_image_id = addImgItem(file_name, size, img_suffixes, is_train)  # 图片信息print('add image with {} and {}'.format(file_name, size))else:raise Exception('duplicated image: {}'.format(file_name))# subelem is <width>, <height>, <depth>, <name>, <bndbox>for subelem in elem:bndbox['xmin'] = Nonebndbox['xmax'] = Nonebndbox['ymin'] = Nonebndbox['ymax'] = Nonecurrent_sub = subelem.tagif current_parent == 'object' and subelem.tag == 'name':object_name = subelem.text# if object_name not in category_set:#    current_category_id = addCatItem(object_name)# else:# current_category_id = category_set[object_name]current_category_id = category_set.index(object_name) + 1  # index默认从0开始,但是json文件是从1开始,所以+1elif current_parent == 'size':if size[subelem.tag] is not None:raise Exception('xml structure broken at size tag.')size[subelem.tag] = int(subelem.text)# option is <xmin>, <ymin>, <xmax>, <ymax>, when subelem is <bndbox>for option in subelem:if current_sub == 'bndbox':if bndbox[option.tag] is not None:raise Exception('xml structure corrupted at bndbox tag.')bndbox[option.tag] = int(option.text)# only after parse the <object> tagif bndbox['xmin'] is not None:if object_name is None:raise Exception('xml structure broken at bndbox tag')if current_image_id is None:raise Exception('xml structure broken at bndbox tag')if current_category_id is None:raise Exception('xml structure broken at bndbox tag')bbox = []# xbbox.append(bndbox['xmin'])# ybbox.append(bndbox['ymin'])# wbbox.append(bndbox['xmax'] - bndbox['xmin'])# hbbox.append(bndbox['ymax'] - bndbox['ymin'])print('add annotation with {},{},{},{}'.format(object_name, current_image_id - 1, current_category_id,bbox))addAnnoItem(object_name, current_image_id - 1, current_category_id, bbox, is_train)def copy_img(img_path, file_list, img_suffixes, new_folder):# global train_image_id  # 将train_image_id声明为全局变量# global val_image_id  # 将val_image_id声明为全局变量parent_directory = os.path.dirname(img_path)dest_folder = os.path.join(parent_directory, new_folder)# 创建目标文件夹if not os.path.exists(dest_folder):os.makedirs(dest_folder)for each_file in file_list:file_prefix = os.path.splitext(each_file)[0]old_img_path = os.path.join(img_path, file_prefix + img_suffixes)new_img_path = os.path.join(dest_folder, file_prefix + img_suffixes)shutil.copy(old_img_path, new_img_path)# print(f'已拷贝图片到{new_img_path}')# 更新image_id# if new_folder == 'train':#     train_image_id += 1# else:#     val_image_id += 1def check_image_folder_suffix(folder_path):# 获取文件夹中所有文件的后缀名,并将它们放入一个集合(set)中file_suffixes = set()for file_name in os.listdir(folder_path):if os.path.isfile(os.path.join(folder_path, file_name)):_, file_suffix = os.path.splitext(file_name)file_suffixes.add(file_suffix)# 检查集合中后缀名的数量,如果数量为1,则所有图片都是同一个后缀,返回后缀名,否则报错assert len(file_suffixes) == 1, "图片文件夹中的后缀名不统一"return file_suffixes.pop()if __name__ == '__main__':# 存放img和xml的文件夹img_path = 'data/images'xml_path = 'data/Annotations'# 确保img文件夹中只有一种格式img_suffixes = check_image_folder_suffix(img_path)annotation_folder = os.path.join('data', 'annotations')os.makedirs(annotation_folder, exist_ok=True)# 保存生成的coco格式的json路径train_json_file = os.path.join(annotation_folder, 'instances_train2017.json')val_json_file = os.path.join(annotation_folder, 'instances_val2017.json')# 添加categories部分for categoryname in category_set:addCatItem(categoryname)# 获取所有的XML文件列表xmllist = os.listdir(xml_path)# 按8:2的随机比例划分为两个列表train_list, val_list = split_list_by_ratio(xmllist, ratio=0.8)print(train_list)print('--------------------')print(val_list)# 拷贝图片到新的文件夹copy_img(img_path, train_list, img_suffixes, 'train2017')copy_img(img_path, val_list, img_suffixes, 'val2017')parseXmlFiles(xml_path, train_list, img_suffixes, True)parseXmlFiles(xml_path, val_list, img_suffixes, False)json.dump(coco_train, open(train_json_file, 'w'))json.dump(coco_val, open(val_json_file, 'w'))

运行完成后,则会在data下生成train2017/val2017/annotations文件夹,注意自己准备的coco数据集名字也需要和下面一样,否则的话需要去改下demo-yolo源码:

.
├── ./voc2coco.py
├── ./data
│   ├── ./data/train2017
│   │   ├── ./data/train2017/0.jpg
│   │   ├── ./data/train2017/3.jpg
│   │   ├── ...
│   ├── ./data/val2017
│   │   ├── ./data/val2017/5.jpg
│   │   ├── ./data/val2017/16.jpg
│   │   ├── ...
│   ├── ./data/annotations
│   │   ├── ./data/annotations/instances_train2017.json
│   │   ├── ./data/annotations/instances_val2017.json

最后,将上述的data文件夹,整个移动到datasets文件夹下。

2.安装依赖

按照官方提供的安装方式即可,pythontorch版本使用更高版本,尤其需要注意export PYTHONPATH=$PWD:$PYTHONPATH这一行命令,它是一个Shell命令,用于将当前工作目录(通过 $PWD 获取)添加到python的模块搜索路径中,以便python可以找到并导入在当前工作目录下的自定义模块或库。如果重开终端,则需要重新export一下,如果不想这么麻烦,也可以把他写入到~/.bashrc文件中,记得改下自己的路径;另外,别使用python setup.py install,我之前一直以为是这么安装的,结果一直报包不存在,官方仓库中也有对这一点的讨论https://github.com/tinyvision/DAMO-YOLO/issues/13

(1)安装DAMO-YOLO

git clone https://github.com/tinyvision/DAMO-YOLO.git
cd DAMO-YOLO/
conda create -n DAMO-YOLO python=3.7 -y
conda activate DAMO-YOLO
conda install pytorch==1.7.0 torchvision==0.8.0 torchaudio==0.7.0 cudatoolkit=10.2 -c pytorch
pip install -r requirements.txt
export PYTHONPATH=$PWD:$PYTHONPATH

(2)安装pycocotools

pip install cython;
pip install git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI # for Linux
pip install git+https://github.com/philferriere/cocoapi.git#subdirectory=PythonAPI # for Windows

3.修改配置文件

DAMO-YOLO基础网络共有T/S/M/L等模型,并且还有一些轻量化的网络,根据需要的模型大小,下载对应的torch预训练模型,这里我们以s为例:
在这里插入图片描述
configs/damoyolo_tinynasL25_S.py下找到对应的配置文件,可以按自己的数据集修改下batch_size/base_lr_per_img/image_max_range,ZeroHead下的num_classes以及class_names需要修改为自己的数据集对应数量及名称
在这里插入图片描述

特别注意一点,如果需要使用预训练模型,则需要在文件下加入self.train.finetune_path,后面跟下载好的权重路径:
在这里插入图片描述
如果验证阶段报显存满了的错误,则需要修改下damo/config/base.py下的test的batch_size
在这里插入图片描述

4.训练->验证->推理->导出

单卡训练:

python -m torch.distributed.launch --nproc_per_node=1 tools/train.py -f configs/damoyolo_tinynasL25_S.py

多卡训练:

python -m torch.distributed.launch --nproc_per_node=4 tools/train.py -f configs/damoyolo_tinynasL25_S.py

模型验证:

python -m torch.distributed.launch --nproc_per_node=1 tools/eval.py -f configs/damoyolo_tinynasL25_S.py -c workdirs/damoyolo_tinynasL25_S/latest_ckpt.pth --fuse --conf 0.25 --nms 0.45

模型推理:

python -m torch.distributed.launch --nproc_per_node=1 tools/demo.py -p datasets/JPEGImages/11.jpg -f configs/damoyolo_tinynasL25_S.py --engine workdirs/damoyolo_tinynasL25_S/latest_ckpt.pth --infer_size 640 640

模型导出:

# onnx export 
python tools/converter.py -f configs/damoyolo_tinynasL25_S.py -c workdirs/damoyolo_tinynasL25_S/latest_ckpt.pth --batch_size 1 --img_size 640# trt export
python tools/converter.py -f configs/damoyolo_tinynasL25_S.py -c workdirs/damoyolo_tinynasL25_S/latest_ckpt.pth --batch_size 1 --img_size 640 --trt --end2end --trt_eval

5.onnxruntime推理

进入damo-onnx文件夹,修改infer.py的相关参数,主要是模型路径、图片路径等,代码如下,该代码会遍历文件夹中的所有图片,对其进行推理后保存在指定文件夹中(注意需要修改下damo-onnx/coco_classes.txt下的类别名):

import cv2
import os
import copy
from damoyolo.damoyolo_onnx import DAMOYOLO
import torchdef main():# 指定参数model_path = 'damoyolo/model/damoyolo_tinynasL35_M.onnx'score_th = 0.4nms_th = 0.85coco_classes = get_coco_classes()# 指定输入图片文件夹和输出图片文件夹input_folder = '/home/lzj/ssd2t/01.my_algo/damo-yolo/datasets/data/val2017'output_folder = 'output_images_folder'# 创建输出文件夹if not os.path.exists(output_folder):os.makedirs(output_folder)# 初始化模型model = DAMOYOLO(model_path)print(f"Model loaded: {model_path}")# 遍历输入文件夹中的图片for filename in os.listdir(input_folder):if filename.endswith(('.jpg', '.png', '.jpeg')):image_path = os.path.join(input_folder, filename)image = cv2.imread(image_path)# 进行推理bboxes, scores, class_ids = model(image, nms_th=nms_th)# 绘制结果并保存图片result_image = draw_debug(image, score_th, bboxes, scores, class_ids, coco_classes)output_path = os.path.join(output_folder, filename)cv2.imwrite(output_path, result_image)print(f"Output saved: {output_path}")def draw_debug(image, score_th, bboxes, scores, class_ids, coco_classes):debug_image = copy.deepcopy(image)for bbox, score, class_id in zip(bboxes, scores, class_ids):x1, y1, x2, y2 = int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])if score_th > score:continue# 绘制边界框debug_image = cv2.rectangle(debug_image, (x1, y1), (x2, y2), (0, 255, 0), thickness=2)# 显示类别和分数score = '%.2f' % scoretext = '%s:%s' % (str(coco_classes[int(class_id)]), score)debug_image = cv2.putText(debug_image, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), thickness=2)return debug_imagedef get_coco_classes():with open('coco_classes.txt', 'rt') as f:coco_classes = f.read().rstrip('\n').split('\n')return coco_classesif __name__ == '__main__':main()

至此,已经完成自己的数据集训练damo-yolo模型,并且使用onnx推理;完整代码已经放在github中:https://github.com/ZhijunLStudio/DAMO-YOLO-ONNX
如果还有tensorrt的推理需求,可以参考这个仓库:https://github.com/hpc203/DAMO-YOLO-detect-onnxrun-cpp-py

参考:
1.https://github.com/tinyvision/DAMO-YOLO
2.https://github.com/Kazuhito00/DAMO-YOLO-ONNX-Sample

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

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

相关文章

Java的环境配置

目录 window系统安装java下载JDK配置环境变量JAVA_HOME 设置PATH设置CLASSPATH 设置测试JDK是否安装成功 Linux&#xff0c;UNIX&#xff0c;Solaris&#xff0c;FreeBSD环境变量设置流行JAVA开发工具使用 Eclipse 运行第一个 Java 程序 window系统安装java 下载JDK 首先我们…

爬虫进阶-反爬破解5(selenium的优势和点击操作+chrome的远程调试能力+通过Chrome隔离实现一台电脑登陆多个账号)

目录 一、selenium的优势和点击操作 二、chrome的远程调试能力 三、通过Chrome隔离实现一台电脑登陆多个账号 一、selenium的优势和点击操作 1.环境搭建 工具&#xff1a;Chrome浏览器chromedriverselenium win用户&#xff1a;chromedriver.exe放在python.exe旁边 MacO…

Unity汉化一个插件 制作插件汉化工具

我是编程一个菜鸟&#xff0c;英语又不好&#xff0c;有的插件非常牛&#xff01;我想学一学&#xff0c;页面全是英文&#xff0c;完全不知所措&#xff0c;我该怎么办啊...尝试在Unity中汉化一个插件 效果&#xff1a; 思路&#xff1a; 如何在Unity中把一个自己喜欢的插件…

新装Ubuntu系统的一些配置

背景&#xff1a; 最近办公要在Ubuntu系统上进行&#xff0c;于是自己安装了一个Ubuntu22.04系统&#xff0c;记录下新系统做的一些基本配置。 环境 &#xff1a; 系统&#xff1a;Ubuntu-22.04内核&#xff1a;6.2.0-26-generic架构&#xff1a;x86_64 一、 配置root密码 新…

Centos7 完全断网离线环境下安装MySQL 8.0.33 图文教程

Centos7 完全断网离线环境安装MySQL 8.0.33 图文教程 1.1前言1.2 下载离线安装包1.3 将下载好的离线安装包上传到Centos 7 服务器1.3.1 方式一:联网环境下可利用rz命令进行文件上传1.3.2 方式二:断网环境下使用 XFtp 等软件工具进行上传1.4 解压安装包1.5 执行安装脚本1.6 重…

Linux TCP和UDP协议

目录 TCP协议TCP协议的面向连接1.三次握手2.四次挥手 TCP协议的可靠性1.TCP状态转移——TIME_WAIT 状态TIME_WAIT 状态存在的意义&#xff1a;&#xff08;1&#xff09;可靠的终止TCP连接。&#xff08;2&#xff09;让迟来的TCP报文有足够的时间被识别并被丢弃。 2.应答确认、…

信息安全技术概论-李剑-持续更新

图片和细节来源于 用户 xiejava1018 一.概述 随着计算机网络技术的发展&#xff0c;与时代的变化&#xff0c;计算机病毒也经历了从早期的破坏为主到勒索钱财敲诈经济为主&#xff0c;破坏方式也多种多样&#xff0c;由早期的破坏网络到破坏硬件设备等等 &#xff0c;这也…

类和对象:构造函数,析构函数与拷贝构造函数

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器…

Python之线程Thread(一)

一、什么是线程 线程(Thread)特点: 线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;…

elasticsearch的索引库操作

索引库就类似数据库表&#xff0c;mapping映射就类似表的结构。我们要向es中存储数据&#xff0c;必须先创建“库”和“表”。 mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数据类型&#xff0c;常见的…

【C++漂流记】一文搞懂类与对象中的对象特征

在C中&#xff0c;类与对象是面向对象编程的基本概念。类是一种抽象的数据类型&#xff0c;用于描述对象的属性和行为。而对象则是类的实例&#xff0c;具体化了类的属性和行为。本文将介绍C中类与对象的对象特征&#xff0c;并重点讨论了对象的引用。 文章目录 一、构造函数和…

Python入门教程35:使用email模块发送HTML和图片邮件

smtplib模块实现邮件的发送功能&#xff0c;模拟一个stmp客户端&#xff0c;通过与smtp服务器交互来实现邮件发送的功能&#xff0c;可以理解成Foxmail的发邮件功能&#xff0c;在使用之前我们需要准备smtp服务器主机地址、邮箱账号以及密码信息。 #我的Python教程 #官方微信公…

什么是 DNS 隧道以及如何检测和防止攻击

什么是 DNS 隧道&#xff1f; DNS 隧道是一种DNS 攻击技术&#xff0c;涉及在 DNS 查询和响应中对其他协议或程序的信息进行编码。DNS 隧道通常具有可以锁定目标 DNS 服务器的数据有效负载&#xff0c;允许攻击者管理应用程序和远程服务器。 DNS 隧道往往依赖于受感染系统的…

sklearn中的数据集使用

导库 from sklearn.datasets import load_iris 实现 # 加载数据集 iris load_iris() print(f查看数据集&#xff1a;{iris}) print(f查看数据集的特征&#xff1a;{iris.feature_names}) print(f查看数据集的标签&#xff1a;{iris.target_names}) print(f查看数据集的描述…

linux 安装Docker

# 1、yum 包更新到最新 yum update # 2、安装需要的软件包&#xff0c; yum-util 提供yum-config-manager功能&#xff0c;另外两个是devicemapper驱动依赖的 yum install -y yum-utils device-mapper-persistent-data lvm2 # 3、 设置yum源 yum-config-manager --add-repo h…

Lua01——概述

Lua是啥&#xff1f; 官网 https://www.lua.org Lua这个名字在葡萄牙语中的意思是“美丽的月亮”&#xff0c;诞生于巴西的大学实验室。 这是一个小巧、高效且能够很好的和C语言一起工作的编程语言。 在脚本语言领域中&#xff0c;Lua因为有资格作为游戏开发的备选方案&…

51单片机项目(10)——基于51单片机的电压计

本次设计的电压计&#xff0c;使用ADC0832芯片&#xff0c;测到电压后&#xff0c;将电压信息发送到串口进行显示。仿真功能正常&#xff0c;能够运行。&#xff08;工程文件和代码放在最后&#xff09; 电路图如下&#xff1a; 运行过程如下&#xff1a; ADC0832介绍&#xff…

「网页开发|前端开发|Vue」07 前后端分离:如何在Vue中请求外部数据

本文主要介绍两种在Vue中访问外部API获取数据的方式&#xff0c;通过让Vue通过项目外部的接口来获取数据&#xff0c;而不是直接由项目本身进行数据库交互&#xff0c;可以实现前端代码和后端代码的分离&#xff0c;让两个部分的代码编写更独立高效。 文章目录 本系列前文传送…

Flink CDC 菜鸟教程 -环境篇

本教程将介绍如何使用 Flink CDC 来实现这个需求, 在 Flink SQL CLI 中进行,只涉及 SQL,无需一行 Java/Scala 代码,也无需安装 IDE。 系统的整体架构如下图所示: 环境篇 1、 准备一台Linux 2、准备教程所需要的组件 下载 flink-1.13.2 并将其解压至目录 flink-1.13.2 …

CSS学习笔记05

CSS笔记05 定位 position CSS 属性position - 用于指定一个元素在文档中的定位方式。top&#xff0c;right&#xff0c;bottom 和 left 属性则决定了该元素的最终位置。position 有以下常用的属性值&#xff1a; position: static; - 默认值。指定元素使用正常的布局行为&am…