【云驻共创】ModelBox隔空作画 绘制你的专属画作

上进小菜猪 发表于 2022/06/24 15:02:40 2022/06/24
【摘要】 ModelBox 作为于端边云场景提供的 AI 应用开发与运行框架,现在已经越来越多的人投入学习当中了。现在让我们一起用ModelBox隔空作画,绘制你的专属画作!

ModelBox 作为于端边云场景提供的 AI 应用开发与运行框架,现在已经越来越多的人投入学习当中了。现在让我们一起用ModelBox隔空作画,绘制你的专属画作!

一. 什么是ModelBox

ModelBox 是适用于端边云场景提供的 AI 应用开发与运行框架。 它提供了一个基于 Pipeline 的并行执行流程,能够帮助 AI 开发者快速完成模型文件到 AI 推理应用的开发和上线工作,降低 AI 算法落地门槛,同时带来 AI 应用的高稳定性和极致性能。

1.1 ModelBox特点

1.易于开发

AI推理业务可视化编排开发,功能模块化,丰富组件库;c++,python多语言支持。

2.易于集成

集成云上对接的组件,云上对接更容易。

3.高性能,高可靠

pipeline并发运行,数据计算智能调度,资源管理调度精细化,业务运行更高效。

4.软硬件异构

CPUGPUNPU多异构硬件支持,资源利用更便捷高效。

5.全场景

视频语音文本NLP全场景,专为服务化定制,云上集成更容易,端边云数据无缝交换。

6.易于维护

服务运行状态可视化,应用,组件性能实时监控,优化更容易。

1.2 ModelBox解决的问题

目前AI应用开发时,训练完成模型后,需要将多个模型和应用逻辑串联在一起组成AI应用,并上线发布成为服务或应用。在整个过程中,需要面临复杂的应用编程问题:

二. 适配ModelBox框架,降低开发者上手难度

2.1为什么我们需要使用开发板

实现AI应用端侧部署

1. 快速了解学习系统的硬件和软件

2. 学习AI应用端侧硬件开发流程

3. 实现云侧AI应用端侧部岩

ModelBox端云协同Al开发套件,降低门槛的AI开发板

适配ModelBox框架,实现AI推理业务可视化编排开发,拥有0.8TOPS算力,支持一键式AI技能部署实现AI应用快速运行:具有强大的开发者社区,可帮助开发者根据场景定制AI算法。


2.2 AI应用的开发模式

① API模式:

②图编排模式:


2.3 ModelBox中的基本概念

。流程图:有向图,表达应用逻辑,控制ModelBox执行过程,.采用Graphviz DOT语言进行表述。

。功能单元:流程图中的顶点,应用的基本组成部分,ModelBox的执行单元,开发者主要开发的组件ModelBox All应用开发流程:

        。流程图设计:定义业务流程,划分功能单元,理顺功能单元间的数据传递关系。

。功能单元开发:采用框架已提供的通用功能单元,实现自己定义的业务功能单元。

。运行与测试:使用图片、视频文件、实时视频流等测试应用。

三. 开发案例

案例链接:RK3568模型转换验证案例 (huaweicloud.com)

本案例以yolox_nano预训练模型为例,展示Pytorch->onnx->rknn的模型转换与验证全流程。

3.1开发环境部署

使用开发板进行ModelBox AI应用开发有两种方式,一是开发板连接显示器和键盘鼠标,安装Ubuntu桌面,直接在开发板上进行开发;二是使用远程连接工具(如VS Code中的Remote-SSH)从PC端登录开发板进行开发。这里我们推荐第二种方式,因为PC端可以使用功能更丰富、界面更友好的IDE。

3.1.1 配置网络

PC连接开发板需要知道开发板的ip,但是开发板默认没有固定ip,我们提供了ModelBox PC Tool,可以自动为开发板配置ip,也可以在推理阶段很方便的进行视频推流拉流。

PC Tool位于SDK的connect_wizard目录中:

双击connect_wizard.exe,在页面中可以看到有两种开发板连接方式,我们使用网线连接开发板的方式:

按照指引断开或连接网线:

等待一小段时间,可以看到来到了第三步,此时开发板已设置为默认ip:192.168.2.111,PC使用该ip即可SSH登录:

3.1.2 远程连接开发板

我们推荐在PC端使用VS Code远程连接开发板来对设备操作。

使用VS Code连接开发板可以参考我们发布的ModelBox 端云协同AI开发套件(RK3568)上手指南 。同时,上手指南也介绍了如何将开发板注册到HiLens管理控制台进行更方便的在线管理。


3.2转换YOLOX预训练模型到onnx

3.2.1描述模型

拉取YOLOX代码

!git clone https://github.com/Megvii-BaseDetection/YOLOX.git

cd YOLOX

部署YOLOX运行环境

!pip install torch==1.9.0 thop loguru torchvision tabulate opencv_python

参考README获取yolox_nano预训练模型:

Model

size

mAPval
0.5:0.95

Params
(M)

FLOPs
(G)

weights

YOLOX-Nano

416

25.8

0.91

1.08

github

!wget https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_nano.pth

根据配置文件加载模型:

from yolox.exp import get_expimport torch







exp = get_exp("./exps/default/yolox_nano.py", None)




model = exp.get_model()

ckpt_file = "yolox_nano.pth"

ckpt = torch.load(ckpt_file, map_location="cpu")

if "model" in ckpt:

ckpt = ckpt["model"]

model.load_state_dict(ckpt)

model.head.decode_in_inference = False

在YOLOX/yolox/models/network_blocks.py中可以看到,模型中Focus层定义为:

import torch.nn as nn




class Focus(nn.Module):

"""Focus width and height information into channel space."""




def __init__(self, in_channels, out_channels, ksize=1, stride=1, act="silu"):

super().__init__()

self.conv = BaseConv(in_channels * 4, out_channels, ksize, stride, act=act)




def forward(self, x):

# shape of x (b,c,w,h) -> y(b,4c,w/2,h/2)

patch_top_left = x[..., ::2, ::2]

patch_top_right = x[..., ::2, 1::2]

patch_bot_left = x[..., 1::2, ::2]

patch_bot_right = x[..., 1::2, 1::2]

x = torch.cat(

(

patch_top_left,

patch_bot_left,

patch_top_right,

patch_bot_right,

),

dim=1,

)

return self.conv(x)

其中步长为2的切片操作在后续转换到rknn格式时不友好,所以我们用卷积来替代切片操作:

import numpy as np

class FocusInfer(nn.Module):

def __init__(self, conv):

super().__init__()

self.conv = conv

self.top_left = nn.Conv2d(3, 3, 2, 2, groups=3, bias=False)

top_left_weight = torch.Tensor(np.array([[1, 0], [0, 0]]).reshape(1, 1, 2, 2).repeat(3, 0))

self.top_left.weight = torch.nn.Parameter(top_left_weight)

self.top_right = nn.Conv2d(3, 3, 2, 2, groups=3, bias=False)

top_right_weight = torch.Tensor(np.array([[0, 1], [0, 0]]).reshape(1, 1, 2, 2).repeat(3, 0))

self.top_right.weight = torch.nn.Parameter(top_right_weight)

self.bot_left = nn.Conv2d(3, 3, 2, 2, groups=3, bias=False)

bot_left_weight = torch.Tensor(np.array([[0, 0], [1, 0]]).reshape(1, 1, 2, 2).repeat(3, 0))

self.bot_left.weight = torch.nn.Parameter(bot_left_weight)

self.bot_right = nn.Conv2d(3, 3, 2, 2, groups=3, bias=False)

bot_right_weight = torch.Tensor(np.array([[0, 0], [0, 1]]).reshape(1, 1, 2, 2).repeat(3, 0))

self.bot_right.weight = torch.nn.Parameter(bot_right_weight)

def forward(self, x):

top_left = self.top_left(x)

top_right = self.top_right(x)

bot_left = self.bot_left(x)

bot_right = self.bot_right(x)

return self.conv(x)

同样,nn.SiLU操作对后续转换不友好,我们用network_blocks中的SiLU操作替换:

from yolox.models.network_blocks import SiLUfrom yolox.utils import replace_module

focus_infer = FocusInfer(model.backbone.backbone.stem.conv)

model.backbone.backbone.stem = focus_infer

model = replace_module(model, nn.SiLU, SiLU)

model.eval().to("cpu")

print(model.backbone.backbone.stem)

可以看到,操作已替换成功,接下来将预训练模型导出为onnx格式

dummy_input = torch.randn(1, 3, 288, 512)

torch.onnx._export(

model,

dummy_input,

"yolox_nano.onnx",

opset_version=12,)

安装rknn-toolkit2

在上述步骤中,我们已经将预训练模型转为onnx格式,接下来我们返回上级目录安装RK3568适用的模型转换工具:rknn-toolkit2

cd ..

import moxing as moximport os

if not os.path.exists("rknn_toolkit2-1.2.0_f7bb160f-cp36-cp36m-linux_x86_64.whl"):

mox.file.copy('obs://liuyu291/Notebook/rk3568-model/rknn_toolkit2-1.2.0_f7bb160f-cp36-cp36m-linux_x86_64.whl',

'rknn_toolkit2-1.2.0_f7bb160f-cp36-cp36m-linux_x86_64.whl')

由于rknn-toolkit2只提供了python3.6环境下的安装包,所以我们创建一个python3.6的kernel。

首先通过conda创建一个python3.6的虚拟环境py36:

!/home/ma-user/anaconda3/bin/conda create -n py36 python=3.6 -y

接下来安装依赖包:

!/home/ma-user/anaconda3/envs/py36/bin/pip install ipykernel

添加kernel配置文件,使虚拟环境可以在notebook中被识别:

import jsonimport os

data = {

"display_name": "Python36",

"env": {

"PATH": "/home/ma-user/anaconda3/envs/py36/bin:/home/ma-user/anaconda3/envs/python-3.7.10/bin:/modelarts/authoring/notebook-conda/bin:/opt/conda/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/ma-user/modelarts/ma-cli/bin:/home/ma-user/modelarts/ma-cli/bin:/home/ma-user/anaconda3/envs/PyTorch-1.4/bin"

},

"language": "python",

"argv": [

"/home/ma-user/anaconda3/envs/py36/bin/python",

"-m",

"ipykernel",

"-f",

"{connection_file}"

]

}

if not os.path.exists("/home/ma-user/anaconda3/share/jupyter/kernels/py36/"):

os.mkdir("/home/ma-user/anaconda3/share/jupyter/kernels/py36/")

with open('/home/ma-user/anaconda3/share/jupyter/kernels/py36/kernel.json', 'w') as f:

json.dump(data, f, indent=4)

完成后可以看到右上角kernel列表中可以选择到Python36环境(如果没有,可以稍等一下或者刷新一下):

选择该kernel,验证一下python版本和pip版本:

!python -V

!pip -V

接下来安装必要依赖后,安装rknn-toolkit2:

!pip install numpy

!pip install rknn_toolkit2-1.2.0_f7bb160f-cp36-cp36m-linux_x86_64.whl

查看rknn-toolkit2,确认正确安装:

!pip show rknn-toolkit2

安装完成后就可以使用工具包进行模型转换与验证,rknn-toolkit2的使用文档位于https://github.com/rockchip-linux/rknn-toolkit2/tree/master/doc

%%python

from rknn.api import RKNN




rknn = RKNN(verbose=False)

print('--> Config model')

rknn.config(mean_values=[[0., 0., 0.]], std_values=[[1., 1., 1.]])print('done')

print('--> Loading model')

ret = rknn.load_onnx("YOLOX/yolox_nano.onnx")

if ret != 0:

print('Load failed!')

exit(ret)print('done')

print('--> Building model')

ret = rknn.build(do_quantization=False)if ret != 0:

print('Build failed!')

exit(ret)print('done')

print('--> Export RKNN model')

ret = rknn.export_rknn("yolox_nano_288_512.rknn")if ret != 0:

print('Export failed!')

exit(ret)print('done')




rknn.release()

在上述步骤中我们已经成功获得了yolox_nano_288_512.rknn模型,接下来在rknn-toolkit2自带的模拟器中对该模型进行验证。

安装一些运行demo依赖的包:

!pip install loguru thop tabulate pycocotools

%%pythonimport cv2import numpy as npimport sys

sys.path.append("YOLOX")from rknn.api import RKNNfrom yolox.utils import demo_postprocess, multiclass_nms, visfrom yolox.data.data_augment import preproc as preprocessfrom yolox.data.datasets import COCO_CLASSES

rknn = RKNN(False)

rknn.config(mean_values=[[0., 0., 0.]], std_values=[[1., 1., 1.]])

ret = rknn.load_onnx("YOLOX/yolox_nano.onnx")

ret = rknn.build(do_quantization=False)

ret = rknn.init_runtime()




img = cv2.imread("YOLOX/assets/dog.jpg")

start_img, ratio = preprocess(img, (288, 512), swap=(0, 1, 2))

outputs = rknn.inference(inputs=[start_img])

outputs = outputs[0].squeeze()

predictions = demo_postprocess(outputs, (288, 512))

boxes = predictions[:, :4]

scores = predictions[:, 4:5] * predictions[:, 5:]




boxes_xyxy = np.ones_like(boxes)

boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2.

boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2.

boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2.

boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2.

boxes_xyxy /= ratio




dets = multiclass_nms(boxes_xyxy, scores, nms_thr=0.45, score_thr=0.5)

if dets is not None:

final_boxes = dets[:, :4]

final_scores, final_cls_inds = dets[:, 4], dets[:, 5]

img = vis(img, final_boxes, final_scores, final_cls_inds, conf=0.5, class_names=COCO_CLASSES)

cv2.imwrite("result.jpg", img)

rknn.release()

可以看到,结果图片result.jpg已经成功保存到当前目录,查看结果:

import cv2from matplotlib import pyplot as plt

%matplotlib inline




img = cv2.imread("result.jpg")

plt.imshow(img[:,:,::-1])

plt.show()

可以看到,rknn模型推理结果正确,模型转换验证结束。

如需下载转换好的模型,只需右键单击对应模型下载即可。

3.2.2通用单元

Python通用单元需要提供独立的toml配置文件,指定python功能单元的基本属性。一般情况,目录结构为:

[FlowUnitName]

|---[FlowUnitName].toml

|---[FlowUnitName].py

|---xxx.py

相较于推理单元而言,通用单元不但有配置文件,还需要完善具体的功能代码,以yolox_post为例,首先是功能单元配置文件:

# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.




# Basic config[base]

name = "yolox_post" # The FlowUnit name

device = "cpu" # The flowunit runs on cpu

version = "1.0.0" # The version of the flowunit

type = "python" # Fixed value, do not change

description = "description" # The description of the flowunit

entry = "yolox_post@yolox_postFlowUnit" # Python flowunit entry function

group_type = "generic" # flowunit group attribution, change as input/output/image ...




# Flowunit Type

stream = false # Whether the flowunit is a stream flowunit

condition = true # Whether the flowunit is a condition flowunit

collapse = false # Whether the flowunit is a collapse flowunit

collapse_all = false # Whether the flowunit will collapse all the data

expand = false # Whether the flowunit is a expand flowunit




# The default Flowunit config[config]

item = "value"




# Input ports description[input][input.input1] # Input port number, the format is input.input[N]

name = "in_image" # Input port name

type = "uint8" # input port data type ,e.g. float or uint8

device = "cpu" # input buffer type[input.input2] # Input port number, the format is input.input[N]

name = "in_feat" # Input port name

type = "uint8" # input port data type ,e.g. float or uint8

device = "cpu" # input buffer type




# Output ports description[output][output.output1] # Output port number, the format is output.output[N]

name = "has_hand" # Output port name

type = "float" # output port data type ,e.g. float or uint8[output.output2] # Output port number, the format is output.output[N]

name = "no_hand" # Output port name

type = "float" # output port data type ,e.g. float or uint8

Basic config是一些单元名等基本配置,Flowunit Type是功能单元类型,yolox_post是一个条件单元,所以可以看到condition为true,此外还有一些展开、归拢等性质,可以在AI Gallery ModelBox )板块下看到更多案例。

config为单元需要配置的一些属性,如本单元需要一些特征图size、阈值等信息,所以在配置文件中修改config为:

[config]

net_h = 320

net_w = 320

num_classes = 2

conf_threshold = 0.5

iou_threshold = 0.5

此外,输入输出type根据实际逻辑可能进行一些修改:

# Input ports description[input][input.input1] # Input port number, the format is input.input[N]

name = "in_image" # Input port name

type = "uint8" # input port data type ,e.g. float or uint8

device = "cpu" # input buffer type

[input.input2] # Input port number, the format is input.input[N]

name = "in_feat" # Input port name

type = "float" # input port data type ,e.g. float or uint8

device = "cpu" # input buffer type




# Output ports description[output][output.output1] # Output port number, the format is output.output[N]

name = "has_hand" # Output port name

type = "uint8" # output port data type ,e.g. float or uint8

[output.output2] # Output port number, the format is output.output[N]

name = "no_hand" # Output port name

type = "uint8" # output port data type ,e.g. float or uint8

接下来,我们查看yolox_post.py,可以看到创建单元时已经生成了基本接口:

# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.

#!/usr/bin/env python# -*- coding: utf-8 -*-import _flowunit as modelbox

class yolox_postFlowUnit(modelbox.FlowUnit):

# Derived from modelbox.FlowUnit

def __init__(self):

super().__init__()




def open(self, config):

# Open the flowunit to obtain configuration information

return modelbox.Status.StatusCode.STATUS_SUCCESS




def process(self, data_context):

# Process the data

in_data = data_context.input("in_1")

out_data = data_context.output("out_1")




# yolox_post process code.

# Remove the following code and add your own code here.

for buffer in in_data:

response = "Hello World " + buffer.as_object()

result = response.encode('utf-8').strip()

add_buffer = modelbox.Buffer(self.get_bind_device(), result)

out_data.push_back(add_buffer)




return modelbox.Status.StatusCode.STATUS_SUCCESS




def close(self):

# Close the flowunit

return modelbox.Status()




def data_pre(self, data_context):

# Before streaming data starts

return modelbox.Status()




def data_post(self, data_context):

# After streaming data ends

return modelbox.Status()




def data_group_pre(self, data_context):

# Before all streaming data starts

return modelbox.Status()




def data_group_post(self, data_context):

# After all streaming data ends

return modelbox.Status()

如果功能单元的工作模式是stream = false时,功能单元会调用openprocessclose接口;如果功能单元的工作模式是stream = true时,功能单元会调用opendata_group_predata_preprocessdata_postdata_group_postclose接口;用户可根据实际需求实现对应接口。

根据单元性质,我们主要需要完善openprocess接口:

import _flowunit as modelboximport numpy as np from yolox_utils import postprocess, expand_bboxes_with_filter, draw_color_palette




class yolox_postFlowUnit(modelbox.FlowUnit):

# Derived from modelbox.FlowUnit

def __init__(self):

super().__init__()




def open(self, config):

self.net_h = config.get_int('net_h', 320)

self.net_w = config.get_int('net_w', 320)

self.num_classes = config.get_int('num_classes', 2)

self.num_grids = int((self.net_h / 32) * (self.net_w / 32)) * (1 + 2*2 + 4*4)

self.conf_thre = config.get_float('conf_threshold', 0.3)

self.nms_thre = config.get_float('iou_threshold', 0.4)

return modelbox.Status.StatusCode.STATUS_SUCCESS




def process(self, data_context):

modelbox.info("YOLOX POST")

in_image = data_context.input("in_image")

in_feat = data_context.input("in_feat")




has_hand = data_context.output("has_hand")

no_hand = data_context.output("no_hand")




for buffer_img, buffer_feat in zip(in_image, in_feat):

width = buffer_img.get('width')

height = buffer_img.get('height')

channel = buffer_img.get('channel')




img_data = np.array(buffer_img.as_object(), copy=False)

img_data = img_data.reshape((height, width, channel))




feat_data = np.array(buffer_feat.as_object(), copy=False)

feat_data = feat_data.reshape((self.num_grids, self.num_classes + 5))




ratio = (self.net_h / height, self.net_w / width)

bboxes = postprocess(feat_data, (self.net_h, self.net_w), self.conf_thre, self.nms_thre, ratio)

box = expand_bboxes_with_filter(bboxes, width, height)




if box:

buffer_img.set("bboxes", box)

has_hand.push_back(buffer_img)




else:

draw_color_palette(img_data)

img_buffer = modelbox.Buffer(self.get_bind_device(), img_data)

img_buffer.copy_meta(buffer_img)

no_hand.push_back(img_buffer)




return modelbox.Status.StatusCode.STATUS_SUCCESS




def close(self):

# Close the flowunit

return modelbox.Status()

可以看到,在open中我们进行了一些参数获取,process进行逻辑处理,输入输出可以通过data_context来获取,值得注意的是输出时我们返回的是图,在检测到手时为图附加了检测框信息,该信息可以被下一单元获取。

同样的,完善其余通用功能单元,具体可以参考我们提供的代码。


3.2.3应用运行

我们需要准备一个mp4文件拷贝到data文件夹下,我们提供了测试视频hand.mp4,然后打开工程目录下bin/mock_task.toml文件,修改其中的任务输入和任务输出配置为如下内容:

# 任务输入,mock模拟目前仅支持一路rtsp或者本地url

# rtsp摄像头,type = "rtsp", url里面写入rtsp地址

# 其它用"url",比如可以是本地文件地址, 或者httpserver的地址,(摄像头 url = "0")[input]

type = "url"

url = "../data/hand.mp4"




# 任务输出,目前仅支持"webhook", 和本地输出"local"(输出到屏幕,url="0", 输出到rtsp,填写rtsp地址)

# (local 还可以输出到本地文件,这个时候注意,文件可以是相对路径,是相对这个mock_task.toml文件本身)[output]

type = "local"

url = "../hilens_data_dir/paint.mp4"

配置好后在工程路径下执行build_project.sh进行工程构建:

rock@rock-3a:~/lxy/examples$ cd workspace/hand_painting/

rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./build_project.sh

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/hand_painting.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/modelbox.conf to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/extract_roi/extract_roi.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/painting/painting.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/yolox_post/yolox_post.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/hand_detection/hand_detection.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/pose_detection/pose_detection.toml to Unix format...

dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/bin/mock_task.toml to Unix format...




build success: you can run main.sh in ./bin folder




rock@rock-3a:~/lxy/examples/workspace/hand_painting$

构建完成后运行项目:

rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./bin/main.sh

等待稍许即可以在hilens_data_dir文件夹下看到运行结果:

除了mp4外我们也支持很多其他类型的输入输出,ModelBox PC TOOL也提供了推流与拉流功能,选择输入实时视频流,启动:

运行程序时配置输出地址为推流地址,即可在本机网页中查看到运行结果:

ModelBox是一个AI应用程序开发和操作框架,适用于终端边缘云场景。它提供了一个基于管道的并行执行过程,可以帮助AI开发人员快速完成从模型文件到AI推理应用的开发和在线工作,降低AI算法的落地门槛,为AI应用带来高稳定性和极高性能。到这里,你学会用ModelBox隔空作画,绘制你的专属画作了吗?


本文参与华为云社区【内容共创】活动第17期。

https://bbs.huaweicloud.com/blogs/358780

任务12ModelBox隔空作画 绘制你的专属画作



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。