【OpenVINO™】在 C# 中使用OpenVINO™ 部署PP-YOLOE实现物体检测

 前言

OpenVINO™ C# API 是一个 OpenVINO™ 的 .Net wrapper,应用最新的 OpenVINO™ 库开发,通过 OpenVINO™ C API 实现 .Net 对 OpenVINO™ Runtime 调用,使用习惯与 OpenVINO™ C++ API 一致。OpenVINO™ C# API 由于是基于 OpenVINO™ 开发,所支持的平台与 OpenVINO™ 完全一致,具体信息可以参考 OpenVINO™。通过使用 OpenVINO™ C# API,可以在 .NET、.NET Framework等框架下使用 C# 语言实现深度学习模型在指定平台推理加速。

OpenVINO™ C# API 项目链接为:

https://github.com/guojin-yan/OpenVINO-CSharp-API.git

项目源码链接为:

https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples.git

1. 简介

   PP-YOLOE是基于PP-YOLOv2的优秀单级无锚模型,超越了各种流行的YOLO模型。PP-YOLOE有一系列型号,命名为s/m/l/x,通过宽度乘数和深度乘数进行配置。PP-YOLOE避免使用特殊的运算符,如可变形卷积或矩阵NMS,以便友好地部署在各种硬件上。 在本文中,我们将使用OpenVINO™ C# API 部署 PP-YOLOE实现物体检测。

2. 项目环境与依赖

  该项目中所需依赖已经支持通过NuGet Package进行安装,在该项目中,需要安装以下NuGet Package:

  • OpenVINO C# API NuGet Package:
OpenVINO.CSharp.API
OpenVINO.runtime.win
OpenVINO.CSharp.API.Extensions
OpenVINO.CSharp.API.Extensions.OpenCvSharp
  • OpenCvSharp NuGet Package:
OpenCvSharp4
OpenCvSharp4.Extensions
OpenCvSharp4.runtime.win

3. 项目输出

  项目使用的是控制台输出,运行后输出如下所示:

<00:00:00> Sending http request to https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/releases/download/Model/ppyoloe_plus_crn_l_80e_coco.tar.
<00:00:02> Http Response Accquired.
<00:00:02> Total download length is 199.68 Mb.
<00:00:02> Download Started.
<00:00:02> File created.
<00:02:03> Downloading: [■■■■■■■■■■] 100% <00:02:03 1.81 Mb/s> 199.68 Mb/199.68 Mb downloaded.
<00:02:03> File Downloaded, saved in E:\GitSpace\OpenVINO-CSharp-API-Samples\model_samples\ppyoloe\ppyoloe_opencvsharp\bin\Release\net6.0\model\ppyoloe_plus_crn_l_80e_coco.tar.
<00:00:00> Sending http request to https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/releases/download/Image/test_det_02.jpg.
<00:00:02> Http Response Accquired.
<00:00:02> Total download length is 0.16 Mb.
<00:00:02> Download Started.
<00:00:02> File created.
<00:00:02> Downloading: [■■■■■■■■■■] 100% <00:00:02 0.06 Mb/s> 0.16 Mb/0.16 Mb downloaded.
<00:00:02> File Downloaded, saved in E:\GitSpace\OpenVINO-CSharp-API-Samples\model_samples\ppyoloe\ppyoloe_opencvsharp\bin\Release\net6.0\model\test_image.jpg.
[ INFO ] Inference device: CPU
[ INFO ] Start RT-DETR model inference.
[ INFO ] 1. Initialize OpenVINO Runtime Core success, time spend: 4.5204ms.
[ INFO ] 2. Read inference model success, time spend: 228.4451ms.
[ INFO ] Inference Model
[ INFO ]   Model name: Model0
[ INFO ]   Input:
[ INFO ]      name: scale_factor
[ INFO ]      type: float
[ INFO ]      shape: Shape : {?,2}
[ INFO ]      name: image
[ INFO ]      type: float
[ INFO ]      shape: Shape : {?,3,640,640}
[ INFO ]   Output:
[ INFO ]      name: multiclass_nms3_0.tmp_0
[ INFO ]      type: float
[ INFO ]      shape: Shape : {?,6}
[ INFO ]      name: multiclass_nms3_0.tmp_2
[ INFO ]      type: int32_t
[ INFO ]      shape: Shape : {?}
[ INFO ] 3. Loading a model to the device success, time spend:501.0716ms.
[ INFO ] 4. Create an infer request success, time spend:0.2663ms.
[ INFO ] 5. Process input images success, time spend:30.1001ms.
[ INFO ] 6. Set up input data success, time spend:2.3631ms.
[ INFO ] 7. Do inference synchronously success, time spend:286.1085ms.
[ INFO ] 8. Get infer result data success, time spend:0.5189ms.
[ INFO ] 9. Process reault  success, time spend:0.4425ms.
[ INFO ] The result save to E:\GitSpace\OpenVINO-CSharp-API-Samples\model_samples\ppyoloe\ppyoloe_opencvsharp\bin\Release\net6.0\model\test_image_result.jpg

  图像预测结果如下图所示:

image-20240505205735624

4. 代码展示

  以下为嘛中所使用的命名空间代码:

using OpenCvSharp.Dnn;
using OpenCvSharp;
using OpenVinoSharp;
using OpenVinoSharp.Extensions;
using OpenVinoSharp.Extensions.utility;
using System.Runtime.InteropServices;
using OpenVinoSharp.preprocess;
using OpenVinoSharp.Extensions.model;
using OpenVinoSharp.Extensions.result;
using OpenVinoSharp.Extensions.process;namespace ppyoloe_opencvsharp
{internal class Program{  ....}
}

  下面为定义的模型预测代码:

  • 一般预测流程:
static void ppyoloe_det(string model_path, string image_path, string device)
{// -------- Step 1. Initialize OpenVINO Runtime Core --------Core core = new Core();// -------- Step 2. Read inference model --------Model model = core.read_model(model_path);OvExtensions.printf_model_info(model);// -------- Step 3. Loading a model to the device --------CompiledModel compiled_model = core.compile_model(model, device);// -------- Step 4. Create an infer request --------InferRequest infer_request = compiled_model.create_infer_request();// -------- Step 5. Process input images --------Mat image = new Mat(image_path); // Read image by opencvsharpfloat[] factor = new float[] { 640.0f / (float)image.Rows, 640.0f / (float)image.Cols };float[] im_shape = new float[] { 640.0f, 640.0f };Mat input_mat = CvDnn.BlobFromImage(image, 1.0 / 255.0, new OpenCvSharp.Size(640, 640), 0, true, false);float[] input_data = new float[640 * 640 * 3];Marshal.Copy(input_mat.Ptr(0), input_data, 0, input_data.Length);// -------- Step 6. Set up input data --------Tensor input_tensor_data = infer_request.get_tensor("image");input_tensor_data.set_shape(new Shape(1, 3, 640, 640));input_tensor_data.set_data<float>(input_data);Tensor input_tensor_factor = infer_request.get_tensor("scale_factor");input_tensor_factor.set_shape(new Shape(1, 2));input_tensor_factor.set_data<float>(factor);// -------- Step 7. Do inference synchronously --------infer_request.infer();// -------- Step 8. Get infer result data --------Tensor output_tensor = infer_request.get_output_tensor(0);int output_length = (int)output_tensor.get_size();float[] output_data = output_tensor.get_data<float>(output_length);// -------- Step 9. Process reault  --------List<Rect> position_boxes = new List<Rect>();List<int> class_ids = new List<int>();List<float> confidences = new List<float>();for (int i = 0; i < 300; ++i){if (output_data[6 * i + 1] > 0.5){class_ids.Add((int)output_data[6 * i]);confidences.Add(output_data[6 * i + 1]);position_boxes.Add(new Rect((int)output_data[6 * i + 2], (int)output_data[6 * i + 3],(int)(output_data[6 * i + 4] - output_data[6 * i + 2]),(int)(output_data[6 * i + 5] - output_data[6 * i + 3])));}}for (int index = 0; index < class_ids.Count; index++){Cv2.Rectangle(image, position_boxes[index], new Scalar(0, 0, 255), 2, LineTypes.Link8);Cv2.Rectangle(image, new OpenCvSharp.Point(position_boxes[index].TopLeft.X, position_boxes[index].TopLeft.Y + 30),new OpenCvSharp.Point(position_boxes[index].BottomRight.X, position_boxes[index].TopLeft.Y), new Scalar(0, 255, 255), -1);Cv2.PutText(image, class_ids[index] + "-" + confidences[index].ToString("0.00"),new OpenCvSharp.Point(position_boxes[index].X, position_boxes[index].Y + 25),HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 0, 0), 2);}string output_path = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(image_path)),Path.GetFileNameWithoutExtension(image_path) + "_result.jpg");Cv2.ImWrite(output_path, image);Slog.INFO("The result save to " + output_path);Cv2.ImShow("Result", image);Cv2.WaitKey(0);
}
  • 编译预处理步骤到模型方式推理模型:
static void ppyoloe_det_with_process(string model_path, string image_path, string device)
{// -------- Step 1. Initialize OpenVINO Runtime Core --------Core core = new Core();// -------- Step 2. Read inference model --------Model model = core.read_model(model_path);OvExtensions.printf_model_info(model);PrePostProcessor processor = new PrePostProcessor(model);Tensor input_tensor_pro = new Tensor(new OvType(ElementType.U8), new Shape(1, 640, 640, 3));InputInfo input_info = processor.input("image");InputTensorInfo input_tensor_info = input_info.tensor();input_tensor_info.set_from(input_tensor_pro).set_layout(new Layout("NHWC")).set_color_format(ColorFormat.BGR);PreProcessSteps process_steps = input_info.preprocess();process_steps.convert_color(ColorFormat.RGB).resize(ResizeAlgorithm.RESIZE_LINEAR).convert_element_type(new OvType(ElementType.F32)).scale(255.0f).convert_layout(new Layout("NCHW"));Model new_model = processor.build();// -------- Step 3. Loading a model to the device --------CompiledModel compiled_model = core.compile_model(new_model, device);// -------- Step 4. Create an infer request --------InferRequest infer_request = compiled_model.create_infer_request();// -------- Step 5. Process input images --------Mat image = new Mat(image_path); // Read image by opencvsharpMat input_image = new Mat();Cv2.Resize(image, input_image, new OpenCvSharp.Size(640, 640));float[] factor = new float[] { 640.0f / (float)image.Rows, 640.0f / (float)image.Cols };float[] im_shape = new float[] { 640.0f, 640.0f };// -------- Step 6. Set up input data --------Tensor input_tensor_data = infer_request.get_tensor("image");byte[] input_data = new byte[3 * 640 * 640];Marshal.Copy(input_image.Ptr(0), input_data, 0, input_data.Length);IntPtr destination = input_tensor_data.data();Marshal.Copy(input_data, 0, destination, input_data.Length);Tensor input_tensor_factor = infer_request.get_tensor("scale_factor");input_tensor_factor.set_shape(new Shape(1, 2));input_tensor_factor.set_data<float>(factor);// -------- Step 7. Do inference synchronously --------infer_request.infer();// -------- Step 8. Get infer result data --------Tensor output_tensor = infer_request.get_output_tensor(0);int output_length = (int)output_tensor.get_size();float[] output_data = output_tensor.get_data<float>(output_length);// -------- Step 9. Process reault  --------List<Rect> position_boxes = new List<Rect>();List<int> class_ids = new List<int>();List<float> confidences = new List<float>();for (int i = 0; i < 300; ++i){if (output_data[6 * i + 1] > 0.5){class_ids.Add((int)output_data[6 * i]);confidences.Add(output_data[6 * i + 1]);position_boxes.Add(new Rect((int)output_data[6 * i + 2], (int)output_data[6 * i + 3],(int)(output_data[6 * i + 4] - output_data[6 * i + 2]),(int)(output_data[6 * i + 5] - output_data[6 * i + 3])));}}for (int index = 0; index < class_ids.Count; index++){Cv2.Rectangle(image, position_boxes[index], new Scalar(0, 0, 255), 2, LineTypes.Link8);Cv2.Rectangle(image, new OpenCvSharp.Point(position_boxes[index].TopLeft.X, position_boxes[index].TopLeft.Y + 30),new OpenCvSharp.Point(position_boxes[index].BottomRight.X, position_boxes[index].TopLeft.Y), new Scalar(0, 255, 255), -1);Cv2.PutText(image, class_ids[index] + "-" + confidences[index].ToString("0.00"),new OpenCvSharp.Point(position_boxes[index].X, position_boxes[index].Y + 25),HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 0, 0), 2);}string output_path = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(image_path)),Path.GetFileNameWithoutExtension(image_path) + "_result.jpg");Cv2.ImWrite(output_path, image);Slog.INFO("The result save to " + output_path);Cv2.ImShow("Result", image);Cv2.WaitKey(0);
}
  • 使用封装的方法:
static void ppyoloe_det_using_extensions(string model_path, string image_path, string device)
{PPYoloeConfig config = new PPYoloeConfig();config.set_model(model_path);PPYoloeDet det = new PPYoloeDet(config);Mat image = Cv2.ImRead(image_path);DetResult result = det.predict(image);Mat result_im = Visualize.draw_det_result(result, image);Cv2.ImShow("Result", result_im);Cv2.WaitKey(0);
}

  下面为程序运行的主函数代码,该代码会下载转换好的预测模型,并调用预测方法进行预测:

static void Main(string[] args)
{string model_path = "";string image_path = "";string device = "CPU";if (args.Length == 0){if (!Directory.Exists("./model")){Directory.CreateDirectory("./model");}if (!File.Exists("./model/model.pdiparams")&& !File.Exists("./model/model.pdmodel")){if (!File.Exists("./model/ppyoloe_plus_crn_l_80e_coco.tar")){_ = Download.download_file_async("https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/releases/download/Model/ppyoloe_plus_crn_l_80e_coco.tar","./model/ppyoloe_plus_crn_l_80e_coco.tar").Result;}Download.unzip("./model/ppyoloe_plus_crn_l_80e_coco.tar", "./model/");}if (!File.Exists("./model/test_image.jpg")){_ = Download.download_file_async("https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/releases/download/Image/test_det_02.jpg","./model/test_image.jpg").Result;}model_path = "./model/model.pdmodel";image_path = "./model/test_image.jpg";}else if (args.Length >= 2){model_path = args[0];image_path = args[1];device = args[2];}else{Console.WriteLine("Please enter the correct command parameters, for example:");Console.WriteLine("> 1. dotnet run");Console.WriteLine("> 2. dotnet run <model path> <image path> <device name>");}// -------- Get OpenVINO runtime version --------OpenVinoSharp.Version version = Ov.get_openvino_version();Slog.INFO("---- OpenVINO INFO----");Slog.INFO("Description : " + version.description);Slog.INFO("Build number: " + version.buildNumber);Slog.INFO("Predict model files: " + model_path);Slog.INFO("Predict image  files: " + image_path);Slog.INFO("Inference device: " + device);Slog.INFO("Start RT-DETR model inference.");ppyoloe_det(model_path, image_path, device);//ppyoloe_det_with_process(model_path, image_path, device);//ppyoloe_det_using_extensions(model_path, image_path, device);
}

5. 总结

  在该项目中,我们结合之前开发的 OpenVINO™ C# API 项目部署PP-YOLOE模型,实现物体检测。

  • 项目完整代码链接为:
https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/blob/master/model_samples/ppyoloe/ppyoloe_opencvsharp/Program.cs
  • 为了方便EmguCV用户使用需求,同时开发了EmguCV版本,项目链接为:
https://github.com/guojin-yan/OpenVINO-CSharp-API-Samples/blob/master/model_samples/ppyoloe/ppyoloe_emgucv/Program.cs

最后如果各位开发者在使用中有任何问题,欢迎大家与我联系。

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

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

相关文章

在实时超声病变检测中挖掘负面时间背景以抑制假阳性

文章目录 Mining Negative Temporal Contexts for False Positive Suppression in Real-Time Ultrasound Lesion Detection摘要挖掘负面时间背景以抑制假阳性&#xff1a;实时超声病变检测的新方法方法实验结果 Mining Negative Temporal Contexts for False Positive Suppress…

谷歌全球倾斜摄影OSGB数据转换正式版V1.0发布!

谷歌全球倾斜摄影OSGB数据转换正式版V1.0来了&#xff01; 五一放假前发布了V0.2版本&#xff0c;在这个版本中&#xff0c;生成了台北地区倾斜摄影OSGB数据&#xff0c;共有335GB&#xff0c;同时针对一些比较严重的问题进行了修复&#xff0c;包括修复瓦片缺失&#xff0c;数…

体重秤蓝牙语音芯片方案-WT2605蓝牙音频ic在电子秤上的应用

在快节奏的现代生活中&#xff0c;健康成为了每个人关注的焦点。而体重作为健康指标之一&#xff0c;更是备受关注。如今&#xff0c;一款全新的智能体重秤蓝牙语音芯片方案正悄然改变着我们的健康管理方式&#xff0c;让健康触手可及。 性能&#xff1a; 1&#xff1a;蓝牙语…

共享充电宝语音芯片ic方案支持远程4g无线更新语音

一、简介 共享充电宝语音芯片ic方案支持远程4g无线wifi蓝牙更新语音 共享充电宝已经是遍布在大街小巷的好产品&#xff0c;解决了携带充电宝麻烦的痛点 但是很多的共享充电宝在人机交互方便&#xff0c;还做得不够好&#xff0c;比如&#xff1a;借、还设备没有语音提示&…

每天认识新职业——网络工程师

一、网络工程师是什么 网络工程师是通过学习和训练&#xff0c;掌握网络技术的理论知识和操作技能的网络技术人员。网络工程师能够从事计算机信息系统的设计、建设、运行和维护工作。相关职业&#xff1a;系统集成工程师、计算机硬件工程师职业其他名称&#xff1a;网络管理员、…

第二证券今日投资参考:VA、VE景气上行 猪价步入慢涨趋势

上周五&#xff0c;沪指午后在地产、金融等板块的带动下震荡上升&#xff0c;创业板指等弱势下探。到收盘&#xff0c;沪指微涨0.01%报3154.55点&#xff0c;深证成指跌0.58%报9731.24点&#xff0c;创业板指跌1.15%报1878.17点&#xff0c;科创50指数跌1.5%&#xff1b;两市算…

力扣HOT100 - 155. 最小栈

解题思路&#xff1a; 辅助栈 class MinStack {private Stack<Integer> stack;private Stack<Integer> min_stack;public MinStack() {stack new Stack<>();min_stack new Stack<>();}public void push(int val) {stack.push(val);if (min_stack.i…

[Go] 结构体不初始化仍然能够调用其方法

文章目录 背景复现原理验证验证2结论参考文档 背景 在写代码的时候&#xff0c;偶然没有将结构体初始化&#xff0c;又调用了该结构体的方法&#xff0c;编译器竟然没有报错&#xff0c;而且运行也是正常的。 复现 写了一个小 demo 用于复现&#xff0c;可以看到&#xff0c…

tomcat 设置JVM 参数

tomcat 启动的服务 设置jvm 设置的文件目录&#xff1a; /tomcat/bin/catalina.sh 添加设置参数&#xff1a; JAVA_OPTS“$JAVA_OPTS -server -Xms1024m -Xmx4096m -XX:MetaspaceSize1024m -XX:MaxMetaspaceSize2048m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/se…

led灯哪个品牌质量好?分享五款耐用又护眼的护眼台灯

led灯哪个品牌质量好&#xff1f;在LED照明日益普及的今天&#xff0c;选择一款质量上乘、耐用且护眼的LED台灯显得尤为重要。本文将为大家推荐五款备受好评的护眼台灯品牌&#xff0c;这些品牌凭借其卓越的照明效果、舒适的视觉体验以及优质的售后服务&#xff0c;成为了市场上…

LLM大语言模型(十五):LangChain的Agent中使用自定义的ChatGLM,且底层调用的是remote的ChatGLM3-6B的HTTP服务

背景 本文搭建了一个完整的LangChain的Agent&#xff0c;调用本地启动的ChatGLM3-6B的HTTP server。 为后续的RAG做好了准备。 增加服务端role&#xff1a;observation ChatGLM3的官方demo&#xff1a;openai_api_demo目录 api_server.py文件 class ChatMessage(BaseModel…

蛋白胨(Peptone)主要应用于微生物发酵领域 我国进口量持续增长

蛋白胨&#xff08;Peptone&#xff09;主要应用于微生物发酵领域 我国进口量持续增长 蛋白胨&#xff08;Peptone&#xff09;是一种有机化合物&#xff0c;分子式为C5H9NO2。蛋白胨外观呈淡黄至棕黄色粉剂&#xff0c;含有肉香气味&#xff0c;易溶于水&#xff0c;不溶于乙醚…

FENDI CLUB精酿啤酒馆与传统啤酒销售模式的不同

精酿啤酒火了&#xff0c;国产品牌精酿也在迅速崛起&#xff0c;为精酿啤酒这一小众品类发展加足了马力。与此同时&#xff0c;精酿酒吧、精酿小酒馆也开始出现了增长。这标志着中国精酿啤酒市场的快速发展和国产品牌的崭新局面。 FENDI CLUB精酿啤酒已经在不少地方开始积极开…

下载文件名称乱码或变成了随机码

如图 后端是有正常返回附件名称的,浏览器开发工具中也正常显示了这个数据,但是下载下来的文件名称确实一堆随机码. 其实这个问题的原因是因为跨域 查看console: Refused to get unsafe header "content-disposition" 现象,后端传递到前端的fileName不能被识别,下载…

python脚本打包封装为EXE文件并加密

背景说明 当你编写好一个python脚本&#xff0c;但不想把源码公开&#xff0c;那如何将脚本分享给其他人呢&#xff1f; 有2种办法&#xff1a; 封装为EXE 将Python脚本打包成可执行文件(exe)&#xff0c;一个常用的工具是PyInstaller。打包过程结束后&#xff0c;在该脚本同…

【cocos creator】2.4.0 import android.support.v4.app.ActivityCompat;失败的解决方案

时间是2024年5月&#xff0c;某cocos creator项目用的是2.4.0编辑器。需求是获取录音权限&#xff0c;需要import ActivityCompat。但是失败&#xff0c;提示Cannot resolve symbol app。 尝试了一些方案失败之后&#xff0c;决定升级cocos creator编辑器版本。升级到2.4.10。…

Leetcode—100296. 两个字符串的排列差【简单】

2024每日刷题&#xff08;135&#xff09; Leetcode—100296. 两个字符串的排列差 实现代码 class Solution { public:int findPermutationDifference(string s, string t) {int maps[26];int mapt[26];for(int i 0; i < s.size(); i) {int idxs s[i] - a;int idxt t[i…

爬虫 Python将网页内容保存为PDF(url转pdf) 譬如下载某个专栏下的全部文章

我看到一个不错的教程&#xff0c;想下载教程下全部文章到本地&#xff0c;有时间看看&#xff0c;但是问了作者没有电子档&#xff0c;就想办法了。 PS: 我一天天的到底在干嘛&#xff01;唉… 需求: 爬取一个网页里全部文章且存为pdf 参考链接: 【已解决】Python将网页内容…

CAD绘制3维场景图记录

文章目录 1.给三维体每个面上不同的颜色1.1 软件设置1.2 着色面 2.在长方体上画圆柱体 1.给三维体每个面上不同的颜色 1.1 软件设置 这里必须改为真实&#xff0c;否则之后的面上色只能显示更改了线条颜色 1.2 着色面 如果你菜单栏没有显示&#xff0c;在上面小倒三角那里…

【启明智显技术分享】SSD202核心板Rootfs下如何烧录mac地址

提示&#xff1a;作为Espressif&#xff08;乐鑫科技&#xff09;大中华区合作伙伴及sigmastar&#xff08;厦门星宸&#xff09;VAD合作伙伴&#xff0c;我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程供开发小伙伴参考。同时也用心整理了乐鑫及星宸…