OpenVINO基本操作流程

news2024/10/4 19:54:04

环境配置:

conda env list:可以查看有哪些环境

conda activate intel:启动某个环境

pip list:可以查看此环境下都下载了哪些软件包 

from openvino.inference_engine import IEcore#从OpenVINO推理引擎中导入IECore类
import numpy as np
import cv2


'''
1,初始化推理引擎
'''
ie = IECore()#创建一个IECore对象
for device in ie.available_devices:#遍历所有可用openVINO的计算机设备
    print(device)#打印每一个设备的名称




with open('imagenet_classes.txt') as f:#打开一个包含ImageNet类标签的文件,读取里面的每一行信息
    labels = [line.strip() for line in f.readlines()]#读取文件中的所有行并去除每行的空格
#readlines()可以把文件里的每一行信息保存在列表中,但是也会把换行符保存进去
#line.strip()可以去掉换行符
#最后把每一行信息都保存在labels(每一行是一个元素,100行则在labels列表中有100个元素)



'''
2,加载模型
'''
model_xml = "resnet18.xml"#指定模型的XML文件路径
model_bin = "resnet18.bin"#指定模型的二进制权重文件路径
#xml指明了这个模型中的层和参数,相当于搭了一个框架。而bin就是框架里的信息
#pytorch训练出来的是.pt文件,后续会通过程序先将.pt文件转为onnx,然后再转为.xml,.bin文件

net = ie.read_network(model=model_xml, weights=model_bin)#读取模型


'''
3,配置输入输出
'''
input_blob = next(iter(net.input_info))#获取模型输入层名称并保存在input_blob中
out_blob = next(iter(net.outputs))#获取模型的输出层名称

n,c,h,w = net.input_info[input_blob].input_data.shape#获取输入数据的形状
#n表示一个批次传入图片数量,c代表通道数
#通过print可以看出如果我们想要把图片传入到resnet18这个网络中,1次只能传一张,通道数为3(彩色)
#传入图片高宽需要是h w
print(n, c, h, w)

#为了匹配上述模型输入图片要求,就有了下面的对图片的处理
src = cv2.imread("1.jpg")#读取图片文件
image = cv2.resize(src, (w,h))#调整图片大小以匹配模型的输入大小
image = np.float32(image)/255.0$将图像数据归一化到[0,1]范围
#np.float32(image)将图片每一个像素值转换为浮点型,然后每个都除以255
image[:,:,] -= (np.float32(0.485), np.float32(0.456), np.float(0.406))#从图像中减去均值
image[:,:,] -= (np.float32(0.229), np.float32(0.224), np.float(0.225))#将图像数据除以标准#
#差
#image[:,:,]读取每一个像素值
image = image.transpose(2, 0, 1)#调整图像的维度顺序,将cv2读取的BGR(012)格式转换为RBG(2,0,1)


exec_net = ie.load_network(network = net, device_name = "CPU")#在CPU上加载模型
res = exec_net.infer(inputs={input_blob:[image]})#进行推断



'''
后处理
'''
res = res[out_blob]#获取推断结果
print(res.shape)#打印推断结果的形状.因为分类给的txt中有1000行,即1000个可能。所以输出的res有#1000个概率,我们只需要找到概率最大的那个即可
label_index = np.argmax(res, 1)[0]#获取最可能的类标签的索引
print(label_index, label[label_index])#打印类标签的索引和名称
cv2.putText(src, labels[label_index],(50,50),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)
#在原图上添加类标签文本
cv2.imshow("image classification", src)
cv2.waitKey(0)

图像分类案例

1,模型训练

使用OpenVINO进行实时图像分类,使用Pytorch训练一个自己的图形分类模型,然后将它部署在树莓派上,并用摄像头进行

训练自己的图形分类模型首先创建文件夹,文件夹内有自己的数据集文件夹(可以命名为Data),Data文件夹中又有text和train文件夹,分别存放要训练的图片。除了数据集文件夹剩下的就是.py代码文件

训练模型.py

import os #os通常用来创建文件夹,遍历目录等更系统有关
import torch
import torch.nn as nn #nn用来修改网络参数
import torch.optim as optim #optim用来网络优化系统的
from torchvision import datasets, models, transforms
#datasets读取自定义数据,models用来导入预训练模型的,transforms用来数据增强等预处理

if __name__ == '__main__': #定义主函数
    #设置设备。判断当前显卡cuda能否使用,不能使用则使用cpu
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    #数据增强和归一化操作
    data_transforms = {
        'train':transforms.Compose(
            transforms.RandomResizedCrop(224), #对图片进行随机裁剪大小为224*224的大小
            transforms.RandomHorizontalFlip(), #随机对图片进行一个水平翻转
            transforms.ToTensor(), #把图片转变为tensor格式才能输入到网络中
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
            #对图片数据进行归一化,前面为均值,后面是标准差
        ]),
        'test':transforms.Compose([
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    }


    #加载数据集
    data_dir = r'data'#相对路径的写法,表示数据集在此.py文件同目录下的data文件中
    image_datasets = {x:datasets.ImageFolder(os.path.join(data_dir, x),
                                            data_transforms[x])
                            for x in ['train', 'test']}
    #os.path.join(data_dir,x)表示路径拼接将数据增强后的图片存入到data_dir中,data_dir称为根目            
    #录,x来自train或者test
    #此操作表示如果数据来自train则进行数据增强train里的方式,如果来自test则进行test的增强方式
    dataloaders = {x:torch.utils.data.DataLoader(image_datasets[x], batch_size=16,
                                                 shuffle=True, num_workers=4)
                            for x in ['train', 'test']}
    #dataloaders将图片传入到训练网络中,分批次传。batch_size表示一批传几张图片,shuffle表示每        
    #次传入数据前进行打乱。num_works表示用几个线程进行加载
    dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'test']}
    #dataset_sizes获取train,test中有多少张图片
    class_names = image_datasets['train'].classes
    #class_names获取每一个类别对应的名字    

    #加载预训练的ResNet18模型
    model = models.resnet18(pretrained = True)
    #用models获取预训练模型,pretrained=True表示拿到ResNet18框架之外,还把权重文件里面的数据也 
    #拿下来。在上述OpenVINO实例中我们知道ResNet18数据集有1000中,而这里我们的数据集只有cat和 
    #dog,所以输出是不一样的,所以我们要将输出的数量换为我们的数量,以下两行为替换方法
    num_ftrs = model.fc.in_features
    model.fc = nn.Linear(num_ftrs, len(class_names))
    #网络弄好了,通过下方一行代码转到设备里面进行运行
    model = model.to(device)


    criterion = nn.CrossEntropyLoss()#训练网络时用到的损失函数,分类问题用此损失函数即可
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    #Adam优化器,要优化参数model.parameters()表示models的全部参数,lr表示学习率


    best_acc = 0.0#初始化最高准确率
    best_epoch = -1#初始化
    best_model_wts = None
    

    #训练模型
    num_epochs = 25 #训练轮数
    for epoch in range(num_epochs):
        #首先输出当前是第几轮
        print(f'Epoch {epoch}/{num_epochs - 1}')
        print('-' * 10)

        #如果当前过程是train则进入训练模式model.train(),否则进入到model.eval()验证模式
        for phase in ['train', 'test']:
            if phase == 'train':
                model.train()
            else:
                model.eval()

            #初始化
            running_loss = 0.0
            running_corrects = 0
            
            #读取dataloaders里面的参数
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)
                   

                #每一个迭代开始之前都对梯度计算进行清零 
                optimizer.zero_grad()

                #如果是在训练阶段就打开计算梯度的工具
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _,preds = torch.max(outputs, 1)#找到最大值并返回结果
                    loss = criterion(outputs, labels)
                
                    if phase == 'train':
                        loss.backward()#如果是在训练阶段就进行反向传播
                        optimizer.step()#并且更新优化器
                running_loss += loss.item() * inputs.size(0)#把loss值累加
                running_corrects += torch.sum(preds == labels.data)#统计正确个数
            #每一轮结束就统计一下loss值和正确率
            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]
            #并显示
            print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
            #如果本轮正确率比以往的都高,则进行替换
            if phase == 'test' and epoch_acc >best_acc:
                best_acc = epoch_acc
                best_epoch = epoch
                best_model_wts = model.state_dict()
            
        print()
    print('Training complete!')
    print(f'Best test accuracy: {best_acc:.4f} at epoch {best_epoch}')
    torch.save(best_model_wts, f'models/best_resnet18_model_epoch_{best_epoch}.pth')










使用预训练的ResNet18对图片进行训练,并且将性能最好的权重保存在本地。如下文件

2,模型转换

先转到onnx,再转到openvino

 pytorch转onnx.py

import torch
import torchvision.models as models

#指定要加载的模型权重文件的路径
model_weights_path = 'models/best_resnet18_model_epoch_17.pth'

#加载ResNet-18模型结构
model = models.resnet18()
model.fc = torch.nn.Linear(model.fc.in_features, 2)
#从文件中加载模型权重
model.load_state_dict(torch.load(model_weights_path))

#确保模型处于评估模式,关闭Dropout和BatchNorm层
model.eval()

#创建一个模拟输入,以便ONNX导出器能够确定输入/输出格式
#这个例子假设输入图像是224X224的三通道图像
x = torch.randn(1, 3, 224, 224, requires_grad = True)

#指定ONNX文件的输出路径
onnx_file_path = r'model\resnet18_catdog.onnx'


#将模型导出为ONNX格式
torch.onnx.export(model, #运行的模型
                  x, #模型输入(或者一个元组,如果有多个输入)
                  onnx_file_path,#保存模型的文件路径
                  export_params=True,#存储训练权重和偏置
                  opset_version=10,#ONNX版本
                  do_constant_folding=True#是否执行常量折叠优化
                )
print(f'Model saved in {onnx_file_path}')

netron工具可以查看onnx里的参数 ,如下

 onnx转OpenVINO

先安装onnx :pip install onnx==1.14.0

再用以下命令进行转换:mo --input_model .\resnet18_catdog.onnx

转换完成的文件路径会给标明(.bin .xml .mapping)

3,模型应用:单张图片分类 

 

from openvino.inference_engine import IEcore
import numpy as np
import cv2


ie = IECore()#创建一个IECore对象
for device in ie.available_devices:
    print(device)

#animal.txt里面存放着分类类别名,一行为一种,此示例为分类cat和dog,所以此文件一共两行
#自己编写,并放在这些.py文件的同一目录下
with open('animal_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]


#之前文件未写r,是由于resnet.xml和resnet.bin就在当前目录下,这里用r并加上路径名来指定#resnet18_catdog.xml文件(因为此xml文件不直接在该目录下,而在该目录的文件夹中)
model_xml = r"models/resnet18_catdog.xml"
model_bin = r"models/resnet18_catdog.bin"

net = ie.read_network(model=model_xml, weights=model_bin)#读取模型


input_blob = next(iter(net.input_info))#获取模型输入层名称并保存在input_blob中
out_blob = next(iter(net.outputs))#获取模型的输出层名称

n,c,h,w = net.input_info[input_blob].input_data.shape
print(n, c, h, w)


src = cv2.imread(r"resources/85.jpg")#读取图片文件
image = cv2.resize(src, (w,h))#调整图片大小以匹配模型的输入大小
image = np.float32(image)/255.0$将图像数据归一化到[0,1]范围
image[:,:,] -= (np.float32(0.485), np.float32(0.456), np.float(0.406))#从图像中减去均值
image[:,:,] -= (np.float32(0.229), np.float32(0.224), np.float(0.225))#将图像数据除以标准#
#差
#image[:,:,]读取每一个像素值
image = image.transpose(2, 0, 1)#调整图像的维度顺序,将cv2读取的BGR(012)格式转换为RBG(2,0,1)


exec_net = ie.load_network(network = net, device_name = "CPU")#在CPU上加载模型
res = exec_net.infer(inputs={input_blob:[image]})#进行推断




res = res[out_blob]
print(res.shape)
label_index = np.argmax(res, 1)[0]
print(label_index, label[label_index])
cv2.putText(src, labels[label_index],(50,50),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)
cv2.imshow("image classification", src)
cv2.waitKey(0)

4,摄像头实时分类

摄像头实时分类.py

from openvino.inference_engine import IEcore
import numpy as np
import cv2
import time #导入time库以计算FPS


ie = IECore()#创建一个IECore对象
for device in ie.available_devices:
    print(device)

with open('animal_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]


model_xml = r"models/resnet18_catdog.xml"
model_bin = r"models/resnet18_catdog.bin"

net = ie.read_network(model=model_xml, weights=model_bin)#读取模型


input_blob = next(iter(net.input_info))#获取模型输入层名称并保存在input_blob中
out_blob = next(iter(net.outputs))#获取模型的输出层名称

n,c,h,w = net.input_info[input_blob].input_data.shape

exec_net = ie.load_network(network = net, device_name = "CPU")#在CPU上加载模型

cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("Error")
    exit()

prev_time = time.time() #在循环开始前初始化时间变量
frame_count = 0
fps = 0 #初始化fps

while True:
    ret, frame = cap.read()
    if not ret:
        print("Failed to grab frame.")
        break
    frame_count +=1
    curr_time = time.time()
    delta_time = curr_time - prev_time

    if delta_time >= 1.0: #每秒更新一次FPS
        fps = frame_count / delta_time
        frame_count = 0
        prev_time = curr_time

    image = cv2.resize(src, (w,h))
    image = np.float32(image)/255.0
    image[:,:,] -= (np.float32(0.485), np.float32(0.456), np.float(0.406))
    image[:,:,] -= (np.float32(0.229), np.float32(0.224), np.float(0.225))
    image = image.transpose(2, 0, 1)


    res = exec_net.infer(inputs={input_blob:[image]})#进行推断
    res = res[out_blob]
    print(res.shape)
    label_index = np.argmax(res, 1)[0]
    print(label_index, label[label_index])
    cv2.putText(frame, {fps:.2f},(50,100),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)
    cv2.putText(frame, labels[label_index],(50,50),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)
    cv2.imshow("image classification", src)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

5,模型加速 (异步加速)

摄像头实时分类_异步加速.py

from openvino.inference_engine import IEcore
import numpy as np
import cv2
import time #导入time库以计算FPS


ie = IECore()#创建一个IECore对象
for device in ie.available_devices:
    print(device)

with open('animal_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]


model_xml = r"models/resnet18_catdog.xml"
model_bin = r"models/resnet18_catdog.bin"

net = ie.read_network(model=model_xml, weights=model_bin)#读取模型


input_blob = next(iter(net.input_info))#获取模型输入层名称并保存在input_blob中
out_blob = next(iter(net.outputs))#获取模型的输出层名称

n,c,h,w = net.input_info[input_blob].input_data.shape

exec_net = ie.load_network(network = net, device_name = "CPU")#在CPU上加载模型

cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("Error")
    exit()

prev_time = time.time() #在循环开始前初始化时间变量
frame_count = 0
fps = 0 #初始化fps

request_id = 0 #不同点
while True:
    ret, frame = cap.read()
    if not ret:
        print("Failed to grab frame.")
        break
    frame_count +=1
    curr_time = time.time()
    delta_time = curr_time - prev_time

    if delta_time >= 1.0: #每秒更新一次FPS
        fps = frame_count / delta_time
        frame_count = 0
        prev_time = curr_time

    image = cv2.resize(src, (w,h))
    image = np.float32(image)/255.0
    image[:,:,] -= (np.float32(0.485), np.float32(0.456), np.float(0.406))
    image[:,:,] -= (np.float32(0.229), np.float32(0.224), np.float(0.225))
    image = image.transpose(2, 0, 1)


    exec_net.start_async(request_id=request_id, inputs={input_blob:[image]})#不同点

    if exec_net.requests[request_id].wait(-1) == 0:
        res = exec_net.requests[request_id].outputs[out_blob]
        label_index = np.argmax(res, 1)[0]

        cv2.putText(frame, {fps:.2f},(50,100),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)
        cv2.putText(frame, labels[label_index],(50,50),cv2.FONT_HERSHEY_SIMPLEX,1.0,(0,0,255),2,8)

    cv2.imshow("image classification", src)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

6,如果用神经加速棒

6,树莓派部署

环境配置

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于GitLab 的持续集成环境

一.技术基础 Gitlab: 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务 GitRunner:用于运行您的作业并将结果发送回GitLab。它与GitLab CI一起使用,GitLab CI是GitLab随附的开源持续集成服务&…

SpringCloud微服务搭建实战

文章目录 前言Nacos配置和项目结构图示数据库和脚本准备seata数据库transaction_database数据库 一、Spring Boot、Spring Cloud、Sping Cloud Alibaba二、seata安装seata配置修改Nacos新建配置文件seata-server.properties 三、创建Microservice-Project项目四、创建api-modul…

【HTML5】html5开篇基础(5)

1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的…

使用MTVerseXR SDK实现VR串流

1、概述​ MTVerseXR SDK 是摩尔线程GPU加速的虚拟现实(VR)流媒体平台,专门用于从远程服务器流式传输基于标准OpenXR的应用程序。MTVerseXR可以通过Wi-Fi和USB流式将VR内容从Windows服务器流式传输到XR客户端设备, 使相对性能低的VR客户端可…

芯片公司产品简介

台湾新唐科技:ARM内核-nuvoTon stc(宏晶):STC 8051 系列、STC32 8051系列、STC32 ARM系列 兆易:Arm Cortex-M内核和RISC-V内核 意法: 乐鑫科技: MCU厂家 GPU厂家 MCU与MPU 引用链接: http://…

AutoCAD学习

AutoCAD学习 最基本操作 命令用途说明空格键确认键也可以是重复刚才的命令回车键也是确认键鼠标右键也可以选择确认LINE、L直线命令绘制直线DLI线性尺寸标注DIMLINEAR鼠标滚轮滚动放大缩小视图界面鼠标中键按住移动视图DAL对齐线性标注DIMALIGNED F8 正交模式ORTHOMODE Tab 切换…

漆包线称重系统/自动称重/项目合作

万界星空科技漆包线行业称重系统实现自动称重的方式主要依赖于现代数字电子称重技术、计算机网络技术以及相关的软件系统的集成。以下是对该系统如何实现自动称重的详细解释: 一、硬件基础 称重设备: 系统采用高精度的电子秤作为称重设备,这…

TOGAF框架中的最佳实践与实施技巧:推动企业数字化转型的实战指南

企业的数字化转型已经成为全球商业界的一大趋势,而实现这一转型不仅需要技术的创新,还需要清晰的架构规划和实施策略。TOGAF(The Open Group Architecture Framework)作为全球广泛应用的企业架构标准,为企业提供了行之…

Pikachu-File Inclusion- 本地文件包含

前端每次挑选篮球明星,都会通过get请求,传了文件名,把页面展示出来,由于文件名时前端传给后台;并且查看源码,没有对参数做限制; 尝试直接从前端修改filename 参数; filename../../../../../../…

24-10-3-读书笔记(二十三)-《一个孤独漫步者的遐想》上([法] 让·雅克·卢梭 [译]陈阳)

文章目录 《一个孤独漫步者的遐想》上([法] 让雅克卢梭 [译]陈阳)卢梭生平大事年表总结 《一个孤独漫步者的遐想》上([法] 让雅克卢梭 [译]陈阳) 十月第三篇,看书看个爽,今天是法国哲学家卢梭晚年的著作《一…

【C++】—— vector模拟实现

vector 接口预览 namespace HL {template<class T>class vector{//迭代器iteratortypedef T* iterator;typedef const T* const_iterator;public://默认成员函数vector();vector(size_t n, const T& val T());vector(int n, const T& val T());vector(const v…

【学习资源】人在环路的机器学习

说明&#xff1a;本文图片和内容来源 Human-in-the-Loop Machine Learning Human-in-the-Loop Machine Learning Active learning and annotation for human-centered AI by Robert (Munro) Monarch, June 2021 介绍Human-in-the-Loop的目标&#xff0c;学习过程&#xff0c…

Redis:通用命令 数据类型

Redis&#xff1a;通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis&#xff0c;在Redis中&#xff0c;key的类型都是字符串&#xff0c;而value有多种类型&#xff0c;每种类型都有自己的操作命…

DMA 正点原子版

就是介绍一下dma&#xff0c;只能内存到外设&#xff0c;外设到内存&#xff0c;内存到内存&#xff0c;不能外设到外设这样进行数据传输 这个是 可以看这个表来查&#xff0c;哪个dma的哪个通道用来传输什么数据&#xff0c;这个是芯片固定好的&#xff0c;只能看表查&#xf…

Geogebra基础篇002—关于Geogebra软件的介绍及与MatLab的区别

为什么要学Geogebra&#xff1f; 因为和MatLab的科学计算相比&#xff0c;GeoGebra重点突出教学展示&#xff0c;对于教师、学生人群来讲再合适不过了&#xff0c;尤其是可以融入到PPT里边呈现交互式动画&#xff0c;想想听众的表情&#xff01;这不就弥补了看到PPT播放数学公…

Python+Matplotlib-高等数学上-P7-例如部分可视化

import numpy as np import matplotlib.pyplot as plt# 设置中文字体&#xff0c;确保中文显示正确 plt.rcParams[font.sans-serif] [SimHei] # 用黑体显示中文 plt.rcParams[axes.unicode_minus] False # 正常显示负号# 设置图形和子图 fig, (ax1, ax2) plt.subplots(2, …

Qt 5开发步骤及实例

目录 界面设计编写相应的计算圆面积代码 界面设计 创建桌面应用程序 得到这样一个树形视图 双击界面文件中的dialog.ui 直接双击控件label改名&#xff0c;然后修改最后一个label的属性 修改这个标签的样式&#xff0c;把frameshape改成Panel&#xff0c;frameshadow改…

LC刷题专题:二叉树;迭代;递归(897、1372、208)

文章目录 897.递增顺序搜索树1372. 二叉树中的最长交错路径208. 实现 Trie (前缀树) 897.递增顺序搜索树 https://leetcode.cn/problems/increasing-order-search-tree/description/ 这道题目本身就是一个简单题&#xff1a;非常容易实现&#xff1a;只需要在递归或者迭代中序…

【Java的SPI机制】Java SPI机制:实现灵活的服务扩展

在Java开发中&#xff0c;SPI&#xff08;Service Provider Interface&#xff0c;服务提供者接口&#xff09;机制是一种重要的设计模式&#xff0c;它允许在运行时动态地插入或更换组件实现&#xff0c;从而实现框架或库的扩展点。本文将深入浅出地介绍Java SPI机制&#xff…

【Godot4.3】复合路径类myPath

概述 之前编写过一个基于指令绘图的类交myPoint&#xff0c;但是只涉及折线段生成。这次我基于SVG的<path>标签路径指令的启发&#xff0c;实现了一个能够获得连续绘制的直线段、圆弧和贝塞尔复合路径的类型myPath。 可以使用绘图指令方法或字符串形式的绘图指令解析来…