【昇腾CANN训练营第二期】【应用营】高玩赛作业:使用MindStudio完成YoLoV5和ResNet50的推理开发

举报
张辉 发表于 2021/08/01 23:41:11 2021/08/01
【摘要】 【昇腾CANN训练营第二期】【应用营】高玩赛作业实操

CANN训练营第二期  应用营 高玩赛作业链接:

https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=143438&extra=page=1

实操作业为:

学习路径:

1、视频课程:MindX应用使能组件介绍

https://www.hiascend.com/zh/activities/cloud2021/live/8120

2、实验课程:使用MindX SDK开发智能质检应用

https://lab.huaweicloud.com/testdetail_531

3、实验课程:MindX SDK + Pytorch yolov5 应用案例详解

https://bbs.huaweicloud.com/forum/thread-118598-1-1.html

课程作业:

在MindStudio中创建MindX SDK模板工程,并完成检测(yolov5)+分类(resnet50,从生昇腾社区ModelZoo获取,TensorFlow和Pytorch的均可)。

MindX SDK模板工程可以放到gitee上,参考附件。

评分规则:

在MindStudio中创建MindX SDK模板工程:10分

并完成检测:30分

分类:15分

推理应用开发:15分


解题思路分析:

作业中“学习路径”中的第二个链接的实验,张小白以前就做过了:

https://bbs.huaweicloud.com/forum/thread-137435-1-1.html

第一个链接的直播,好像讲的也是 MindX,并且也含了第二个链接的实验:

https://www.hiascend.com/zh/activities/cloud2021/live/8120

第三个链接,好像就可以指导作业了:

https://bbs.huaweicloud.com/forum/thread-118598-1-1.html

所以打开第三个链接,咱们就开始做作业吧!

听这次的 高玩赛 老师  @Fate丶SSS 的介绍,高玩赛的镜像跟 新手营的镜像一样,都是image-for-MindX。那么简单了,只需要将 新手营没有删除的 AI1S服务器开机,就可以开始做作业了。


如果你不小心删了,也不要紧请移驾 https://bbs.huaweicloud.com/blogs/285668 完成服务器购买等操作:

并根据 https://bbs.huaweicloud.com/blogs/285675 步骤,完成ResNet50网络的离线模型生成:

下面的操作是基于上述步骤执行完毕的情况下进行。


MindStudio的启动、创建工程和MindStudio配置:

开机-》启动 MindStudio

New Project

Next,选择  选择Sample(detection and classfication)(下图中的箭头请忽略,以文字为准)

点击Finish:

当点击工程中pipeline目录下的Sample.pipeline时,系统会提示,“There is no activated MindX SDK”,因此要配置下MindX SDK的位置:

在MindStudio菜单栏依次点击"Ascend"->"MindX SDK Manager"

在弹出的窗口点击 Install SDK

选择 SDK的路径:

选择online试试:(这是一个坑。。。

它居然在装aarch64的版本,估计是对自己是X86的身份存在怀疑:

点击Finish

明显X86的SDK是不能用的。

还是通过local模式装SDK吧:

检查mindxsdk安装盘的位置:/home/HwHiAiUser/MindX

选择该目录下的run文件:

Next:

Next:

耐心等待安装完毕,点击Finish:

点击OK,这回pipeline的图形化界面终于出现了:

仔细查看pipleline的图形,以及对应的源码(Text):

这个sample。前面是YoLoV3,后面是 ResNet50.

而我们的高玩题目呢?要求前面是YoLoV5,后面是 ResNet50。

所以解题思路很简单:

1.要么一个一个做,比如先只做YoLoV5, 再开个MindX SDK项目只做ResNet50

2.或者把现在的pipeline改一下,前面改为YoLoV5。后面应该不用改了。


跑通YoLoV3+ResNet50:


先把现在的代码编译一下:

菜单栏点击”Build“->"Edit Build Configuration..."

点击build,会生成out目录下的main可执行代码:

运行下试试:选择local Run,按以下内容填写:

点击Run:会告诉你om模型不存在:

需要把 第三周作业( https://bbs.huaweicloud.com/blogs/285675 )转换好的ResNet50的离线模型 resnet50_aipp_tf.om 拷贝过来:

找到本项目所在的目录:

cd ~/AscendProjects/Resnet50

cd models/resnet50

cp /home/HwHiAiUser/MindX/resnet50/model/resnet50_aipp_tf.om .

顺便也把第二周作业( https://bbs.huaweicloud.com/blogs/285668 )生成的YoLoV3离线模型也拷过来:

cd ../yolov3

cp /home/HwHiAiUser/MindX/yolov3/yolov3_tf_bs1_fp16.om .

再重新运行:

运行结果如下:

具体内容看不清,贴出来一下:

2021-07-31 18:40:38 - [INFO] Resnet50 start running...
2021-07-31 18:40:38 - [INFO] Execute command on local:
export LD_LIBRARY_PATH=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib/plugins:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib64:/root/AscendProjects/Resnet50/lib:/root/AscendProjects/Resnet50/lib/plugins:${LD_LIBRARY_PATH} && export GST_PLUGIN_SCANNER=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/libexec/gstreamer-1.0/gst-plugin-scanner && export GST_PLUGIN_PATH=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib/gstreamer-1.0:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib/plugins:/root/AscendProjects/Resnet50/lib/plugins && export MX_SDK_HOME=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1 && cd /root/AscendProjects/Resnet50/out && ./main

Begin to initialize Log.
The output directory(./logs) of logs file exist.
Save logs information to specified directory(./logs).
W0731 18:40:40.195328  5423 main.cpp:130] Results:{"MxpiObject":[{"MxpiClass":[{"classId":163,"className":"beagle","confidence":0.87109375}],"classVec":[{"classId":16,"className":"dog","confidence":0.99641436299999997,"headerVec":[]}],"x0":125.63256800000001,"x1":918.29089399999998,"y0":116.434044,"y1":597.21276899999998}]}
2021-07-31 18:40:41 - [INFO] Run finished, exit status: 0

JSON格式化后的结果如下:

{
	"MxpiObject": [{
		"MxpiClass": [{
			"classId": 163,
			"className": "beagle",
			"confidence": 0.87109375
		}],
		"classVec": [{
			"classId": 16,
			"className": "dog",
			"confidence": 0.99641436299999997,
			"headerVec": []
		}],
		"x0": 125.63256800000001,
		"x1": 918.29089399999998,
		"y0": 116.434044,
		"y1": 597.21276899999998
	}]
}

结果分析:

先进行了 YoLoV3的检测(检测结果放入ClassVec和x0,y0、x1、y1的坐标),然后进行了ResNet50的分类(分类结果放入MxpiClass)。

检测结果是:

在 "x0":125.63256800000001,"x1":918.29089399999998,"y0":116.434044,"y1":597.21276899999998}

这个框内,YoLoV3认为它是dog,ResNet认为它属于beagle(猎兔犬)。

当然,Sample代码并没有画出结果。

所以增加以下代码,画个结果的框吧。

参考 https://bbs.huaweicloud.com/forum/thread-137435-1-1.html  的内容增加OpenCV2的代码。

CMakeLists.txt:

 Header path
include_directories(
    ${MX_SDK_HOME}/include/
    ${MX_SDK_HOME}/opensource/include/
    ${MX_SDK_HOME}/opensource/include/opencv4/
)

# add host lib path
link_directories(
    ${MX_SDK_HOME}/lib/
    ${MX_SDK_HOME}/opensource/lib/
    ${MX_SDK_HOME}/opensource/lib64/
    ${MX_SDK_HOME}/opensource/include/opencv4/
)

add_executable(main main.cpp)

target_link_libraries(main glog mxbase streammanager cpprest mindxsdk_protobuf opencv_world)

main.cpp

#include "opencv4/opencv2/opencv.hpp"

还有:

    web::json::value jsonText = web::json::value::parse(result);
    if (jsonText.is_object()) {
        web::json::object textObject = jsonText.as_object();
        auto itInferObject = textObject.find("MxpiObject");
        if (itInferObject == textObject.end() || (!itInferObject->second.is_array())) {
            return 0;
        }
        auto iter = itInferObject->second.as_array().begin();
        cv::Mat src = cv::imread("../data/test.jpg");
        for (; iter != itInferObject->second.as_array().end(); iter++) {
            if (iter->is_object()) {
                auto modelInferObject = iter->as_object();
                float x0 = 0;
                float x1 = 0;
                float y0 = 0;
                float y1 = 0;
                auto it = modelInferObject.find("x0");
                if (it != modelInferObject.end()) {
                    x0 = float(it->second.as_double());
                }
                it = modelInferObject.find("x1");
                if (it != modelInferObject.end()) {
                    x1 = float(it->second.as_double());
                }
                it = modelInferObject.find("y0");
                if (it != modelInferObject.end()) {
                    y0 = float(it->second.as_double());
                }
                it = modelInferObject.find("y1");
                if (it != modelInferObject.end()) {
                    y1 = float(it->second.as_double());
                }
                cv::Rect rect(x0, y0, x1 - x0, y1 - y0);
                cv::rectangle(src, rect, cv::Scalar(0, 255, 0),5, cv::LINE_8,0);
            }
        }
        cv::imwrite("./result_test.jpg", src);
        std::cout << "result_test.jpg produced in the directory:/home/user/AscendProjects/MyApp/out." << std::endl;
    }

重新编译:

重新执行:

上图绿色的就是YoLoV3标记出来的框。


PipeLine:YoLoV3替换为YoLoV5:

既然 YoLoV3和ResNet50都跑通了。那么把 YoLoV5替换掉 YoLoV3就可以。

参考 https://bbs.huaweicloud.com/forum/thread-118598-1-1.html 

类似 YoLoV3 的编排,对比链接中 YoLoV5的pipeline。我们做个修改:

可以先把  YoLoV3 的pipeline备份下

检视一下,

YoLoV3有aippconfig、coco.names、yolov3_tf_bs1_fp16.cfg还有一个om,另外还有一个libMpYOLOv3PostProcessor.so

看看对应的yolov5是否齐全:

aipp_yolov5.cfg

那就先生成YoLoV5的离线模型。


YoLoV5的离线模型的生成:

mkdir /root/yolov5s_convert
cd /root/yolov5s_convert


wget https://github.com/ultralytics/yolov5/archive/v2.0.tar.gz --no-check-certificate

tar -xzf v2.0.tar.gz


vi yolov5-2.0/models/export.py

修改第48行opset_version为11

下载权重文件 yolov5s.pt

cd yolov5-2.0

wget https://github.com/ultralytics/yolov5/releases/download/v2.0/yolov5s.pt --no-check-certificate

python models/export.py --weights ./yolov5s.pt --img 640 --batch 1

好像还得装个pytorch。

去PyPorch官网下载 Pytorch 1.8.1:

pip3 install torch==1.8.1+cpu torchvision==0.9.1+cpu torchaudio==0.8.1 -f https://download.pytorch.org/whl/lts/1.8/torch_lts.html

重新执行:

python models/export.py --weights ./yolov5s.pt --img 640 --batch 1

报缺_lzma模块:

查资料找解决方案:https://blog.csdn.net/moxiao1995071310/article/details/97399809/

照此办理:

apt-get install libbz2-dev

apt-get install lzma

apt-get install liblzma-dev

重新编译python3.7.5:

 cd /root/Python-3.7.5

./configure --prefix=/usr/local/python3.7.5 --enable-optimizations --enable-shared

...

make

...

需要耐心等待416个test走完。。这是一个漫长的过程:

make install

...

验证下:

下面反复执行:

python models/export.py --weights ./yolov5s.pt --img 640 --batch 1

系统会报缺什么模块,然后我们就按照要求装什么模块:

pip install pyyaml

pip install tqdm

pip install onnx

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

这次再执行:

python models/export.py --weights ./yolov5s.pt --img 640 --batch 1

这到底是成功了还是失败了?

检查了一下,并没有生成onnx文件。应该还是有问题的:

度娘搜索:'google.protobuf.descriptor' has no attribute '_internal_create_key' 

https://blog.csdn.net/lemon4869/article/details/107299879

照此办理:

pip install --upgrade protobuf -i https://pypi.tuna.tsinghua.edu.cn/simple/

再来:

python models/export.py --weights ./yolov5s.pt --img 640 --batch 1

。。。

。。。

这回好像真的生成了onnx文件。

这个onnx文件终于生成了。先撒个小花。。。。

然后继续:

 python3.7 -m onnxsim --skip-optimization yolov5s.onnx yolov5s_sim.onnx

pip install --upgrade pip

pip install onnx-simplifier

对onnx图使用onnx-simplifer进行简化:

python3.7 -m onnxsim --skip-optimization yolov5s.onnx yolov5s_sim.onnx

下载 https://bbs.huaweicloud.com/forum/thread-118598-1-1.html

帖子的mxManufacture_yolov5_example.zip 附件,解压,

上传slicy脚本:modify_yolov5s_slice.py

修改模型Slice算子

python3.7 modify_yolov5s_slice.py yolov5s_sim.onnx

生成 yolov5s_sim_t.onnx

在当前目录下编辑2个文件:

编辑 aipp_yolov5.cfg

编辑trans_onnx2om.sh

听 @Fate丶SSS 老师说, 目前的image-for-mindx镜像已经不需要升级 tranpose_d.patch补丁了。

下面开始做ATC模型转换:

bash trans_onnx2om.sh

转换成功,OM离线模型文件已成功生成。

将与yolov5s_sim_t.om相适应的配置文件yolov5.cfg和标签文件coco.names上传到同一目录:

下面正式把sample.pipelin的YoLoV3的目标检测部分修改为YoLoV5的目标检测部分了!

打开MindStudio,在Models目录下创建一个yolov5的目录,再到终端将前面准备好的文件都复制到这个目录下:

/root/AscendProjects/Resnet50/models/yolov5
cp /root/yolov5s_convert/yolov5-2.0/aipp_yolov5.cfg .
cp /root/yolov5s_convert/yolov5-2.0/coco.names .
cp /root/yolov5s_convert/yolov5-2.0/yolov5s_sim_t.om .
cp /root/yolov5s_convert/yolov5-2.0/yolov5.cfg .

修改 resize部分,改为640X640且等比缩放

"mxpi_imageresize0": {
            "props": {
                "parentName": "mxpi_imagedecoder0",
                "resizeHeight": "640",
                "resizeWidth": "640",
                "resizeType": "Resizer_KeepAspectRatio_Fit"
            },
            "factory": "mxpi_imageresize",
            "next": "mxpi_modelinfer0"
        },


修改infer推理部分,将YoLoV3改为YoLoV5:

"mxpi_modelinfer0": {
            "props": {
                "modelPath": "../models/yolov5/yolov5s_sim_t.om",
                "postProcessConfigPath": "../models/yolov5/yolov5.cfg",
                "labelPath": "../models/yolov5/coco.names",
                "postProcessLibPath": "libMpYOLOv5PostProcessor.so"
            },
            "factory": "mxpi_modelinfer",
            "next": "mxpi_imagecrop0"
        },


为了表示这次处理的是yolov5的结果,我们将main.cpp输出的代码做个修改:

并删除out下的jpg文件:

重新编译:

重新运行:

可以看出,result_test-yolov5文件已生成。

输出结果如下:

2021-08-01 00:45:11 - [INFO] Resnet50 start running...
2021-08-01 00:45:11 - [INFO] Execute command on local:
export LD_LIBRARY_PATH=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib/plugins:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib64:/root/AscendProjects/Resnet50/lib:/root/AscendProjects/Resnet50/lib/plugins:${LD_LIBRARY_PATH} && export GST_PLUGIN_SCANNER=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/libexec/gstreamer-1.0/gst-plugin-scanner && export GST_PLUGIN_PATH=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/opensource/lib/gstreamer-1.0:/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1/lib/plugins:/root/AscendProjects/Resnet50/lib/plugins && export MX_SDK_HOME=/usr/local/Ascend/mindx_sdk/mxVision_2.0.1/linux-x86_64/mxVision-2.0.1 && cd /root/AscendProjects/Resnet50/out && ./main

Begin to initialize Log.
The output directory(./logs) of logs file exist.
Save logs information to specified directory(./logs).
W0801 00:45:12.691449 21204 main.cpp:131] Results:{"MxpiObject":[{"MxpiClass":[{"classId":163,"className":"beagle","confidence":0.837890625}],"classVec":[{"classId":16,"className":"dog","confidence":0.672381699,"headerVec":[]}],"x0":73.496948200000006,"x1":939.73120100000006,"y0":132.934708,"y1":603.36547900000005}]}
result_test-yolov5.jpg produced in the directory:/root/AscendProjects/Resnet50/out.
2021-08-01 00:45:13 - [INFO] Run finished, exit status: 0

格式化结果:

{
	"MxpiObject": [{
		"MxpiClass": [{
			"classId": 163,
			"className": "beagle",
			"confidence": 0.837890625
		}],
		"classVec": [{
			"classId": 16,
			"className": "dog",
			"confidence": 0.672381699,
			"headerVec": []
		}],
		"x0": 73.496948200000006,
		"x1": 939.73120100000006,
		"y0": 132.934708,
		"y1": 603.36547900000005
	}]
}

貌似 confidence要比前面YoLoV3的低一点。

前面YoLoV3+ResNet50的结果如下:(前面贴过了,再重贴一遍,便于比较)

{
	"MxpiObject": [{
				"MxpiClass": [{
					"classId": 163,
					"className": "beagle",
					"confidence": 0.87109375
				}],
				"classVec": [{
					"classId": 16,
					"className": "dog",
					"confidence": 0.99641436299999997,
					"headerVec": []
				}],
				"x0": 125.63256800000001,
				"x1": 918.29089399999998,
				"y0": 116.434044,
				"y1": 597.21276899999998
			}


下面考虑在图片中叠加 YoLoV5检测和ResNet50分类的className结果:

需要解析出 

MxpiObject/MxpiClass/className 

MxpiObject/classVec/className 


RapidJSON的安装与使用

由于代码中的json神操作张小白不是很明白,所以自行下载了RapidJSON:http://rapidjson.org/zh-cn/index.html

RapidJSON 是只有头文件的 C++ 库。


github比较慢,换成在 gitee上的镜像

切换到 项目的目录,将include目录复制到项目下:

将 取json报文的和图片叠加文字部分加上去:

#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>

using namespace rapidjson;
using namespace cv;

在取到result之后,保存YoLoV5的className和ResNet50的className:

    std::string myClassName1 , myClassName2;

    rapidjson::Document dom;
    unsigned int i,j;

    if(!dom.Parse(result.c_str()).HasParseError()) {
        if(dom.HasMember("MxpiObject") && dom["MxpiObject"].IsArray()){
            const rapidjson::Value& arr= dom["MxpiObject"];

            for (i = 0; i < arr.Size(); ++i) {
                const rapidjson::Value& obj = arr;

                if(obj.HasMember("MxpiClass") && obj["MxpiClass"].IsArray()){
                    const rapidjson::Value& arr2= obj["MxpiClass"];
                    for (j = 0; j < arr2.Size(); ++j) {
                        const rapidjson::Value& obj2 = arr2[j];
                        if(obj2.HasMember("className") && obj2["className"].IsString()){
                            myClassName1 = obj2["className"].GetString();
                            std::cout << "className:" << myClassName1 << std::endl;
                        }
                        else{
                        }
                    }//end of for j
                }

                if(obj.HasMember("classVec") && obj["classVec"].IsArray()){
                    const rapidjson::Value& arr2= obj["classVec"];
                    for (j = 0; j < arr2.Size(); ++j) {
                        const rapidjson::Value& obj2 = arr2[j];
                        if(obj2.HasMember("className") && obj2["className"].IsString()){
                           myClassName2 = obj2["className"].GetString();
                            std::cout << "className:" << myClassName2 << std::endl;
                        }
                        else{
                        }
                    }//end of for j
                }
            } //end of for i
        }
        else{
        }
    }
    else{
        std::cout << "Parse Result Json string error" << std::endl;
    }

并在最后 画框的时候加上 文字部分:

                cv::Rect rect(x0, y0, x1 - x0, y1 - y0);
                cv::rectangle(src, rect, cv::Scalar(0, 255, 0),5, cv::LINE_8,0);
                cv::putText(src,myClassName1,cv::Point(x0+50,y0+50),FONT_HERSHEY_SIMPLEX,1,cv::Scalar(255,255,0));
                cv::putText(src,myClassName2,cv::Point(x0+50,y0+100),FONT_HERSHEY_SIMPLEX,1,cv::Scalar(255,255,0));


为了可以灵活地切换图片,我们将输入的图片和输出的图片都设置个变量

    // read image file and build stream input
//    std::string picFileName = "../data/test.jpg";
//    std::string picFileName_result = "../out/test_result.jpg";
    std::string picFileName = "../data/test_tiger.jpg";
    std::string picFileName_result = "../out/test_tiger_result.jpg";
//    std::string picFileName = "../data/test_rabbit2.jpg";
//    std::string picFileName_result = "../out/test_rabbit2_result.jpg";

为了可以切换pipeline,我们将pipeline文件也随时做调整

//    std::string pipelineConfigPath = "../pipeline/Sample.pipeline";
//    std::string pipelineConfigPath = "../pipeline/YoloV5.pipeline";
//    std::string pipelineConfigPath = "../pipeline/yoloV3.pipeline";
    std::string pipelineConfigPath = "../pipeline/yoloV3-resnet50.pipeline";

重新编译,运行:

YoloV3+ResNet50认为它是 dog和 beagle(猎兔犬)

从上图可见,类别也在图片中叠加上去了!!

张小白另找了老虎的照片做了如下的试验:

编译:

运行:

YoLoV3认为它是个斑马:zebra

而YoLoV5认为它啥也不是:

而YoLoV3+ResNet50就这样认为:

ResNet50分类倒是对的。确实是老虎。

Fate老师解释说,本来YoLoV3和YoLoV5里面就没有老虎这个选项,所以看不出来反而是对的。如果将其看成是斑马,那反而说明YoloV5比YoloV3的精度要高。。。

额,,,不知道童鞋们Get到没有。。

其实这样高玩赛的作业应该算是做完了吧。

希望同学们能够通过这次CANN训练营应用营的深度学习训练,能够成功使用MindX SDK和MindStudio进行高阶推理。(掌握技能很重要的,也许有个母老虎还需要你去目标检测和分类一下呢。。。)

(全文完,谢谢阅读)

CANN训练营第二期 高玩赛已经开启,请点击:https://bbs.huaweicloud.com/forum/thread-129524-1-1.html

添加下方工作人员微信,添加备注:CANN训练营~ 邀请进群~

6.jpg

模型2.png

算子2.jpg

磊25.jpg

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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