【YOLOv10】使用 TensorRT C++ API 调用GPU加速部署 YOLOv10 实现 500FPS 推理速度——快到飞起!

news2024/11/16 9:51:07

  NVIDIA ® TensorRT ™ 是一款用于高性能深度学习推理的 SDK,包含深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销,为实时目标检测领域带来了新的突破。

  在本文中,我们将演示如何使用NVIDIA TensorRT C++ API 部署YOLOv10目标检测模型,实现模型推理加速。下面看一下YOLOv10模型在TensorRT上的运行效果吧:

YOLOv10实现500FPS推理速度,快到离谱!!——使用 TensorRT C++ API 调用GPU加速部署YOLOv10实现快速预测

1. 前言

  TensorRT是NVIDIA官方推出的一个高性能深度学习推理加速引擎,它能够使深度学习模型在GPU上进行低延迟、高吞吐量的部署。TensorRT是基于CUDA和cuDNN的,专门为NVIDIA的GPU进行了优化。TensorRT支持TensorFlow、PyTorch、Caffe、MxNet等深度学习框架。对于MxNet和PyTorch,需要先将其模型转换为中间模型ONNX格式。总的来说,TensorRT是一个强大的深度学习推理加速引擎,通过优化和部署深度学习模型,能够在各种应用场景中实现快速、高效的推理性能。

tensor-rt

  YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,该方法在Ultralytics Python包的基础上进行了多项创新和改进,主要有以下特点

  1. 消除非极大值抑制(NMS):YOLOv10通过引入一致的双重分配策略,在训练时使用一对多的标签分配来提供丰富的监督信号,在推理时使用一对一的匹配,从而消除了对NMS的依赖。这一改进在保持高精度的同时,减少了推理延迟和计算量。
  2. 全面优化的模型架构:YOLOv10从推理效率和准确性的角度出发,全面优化了模型的各个组成部分。这包括采用轻量级分类头、空间通道去耦下采样和等级引导块设计等,以减少计算冗余并提高模型性能。
  3. 引入大核卷积和部分自注意模块:为了提高性能,YOLOv10在不增加大量计算成本的前提下,引入了大核卷积和部分自注意模块。
  4. 多种模型尺寸可选:官方发布了从N到X各种型号的模型,以满足不同应用的需求。这些模型包括超小型版本YOLOv10-N(用于资源极其有限环境)、小型版本YOLOv10-S(兼顾速度和精度)、中型版本YOLOv10-M(通用)、平衡型版本YOLOv10-B(宽度增加,精度更高)、大型版本YOLOv10-L(精度更高,但计算资源增加)以及超大型版本YOLOv10-X(可实现最高的精度和性能)。

  通过广泛的实验验证,YOLOv10在多个模型尺度上实现了卓越的精度-延迟权衡。例如,在COCO数据集上,YOLOv10-S在相似精度下比其他实时目标检测方法更快,同时参数和浮点运算量也大幅减少。综上所述,YOLOv10通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销,为实时目标检测领域带来了新的突破。

2. 项目开发环境

  下面简单介绍一下项目的开发环境,开发者可以根据自己的设备情况进行配置:

  • 系统平台:Windows 11
  • 开发平台:Visual Studio 2022
  • CUDA:11.4
  • CUDNN:8.2.4
  • TensorRT:8.6
  • OpenCV:4.8.0

  此处代码开发平台使用的是C++,因此在项目配置时,需要配置第三方依赖库,分别是CUDA\CUDNN、TensorRT和OpenCV三个依赖库,其配置方式此处不做详述。

3. 模型获取

3.1 源码下载

  YOLOv10 模型需要源码进行下载,首先克隆GitHub上的源码,输入以下指令:

git clone https://github.com/THU-MIG/yolov10.git
cd yolov10

3.2 配置环境

  接下来安装模型下载以及转换环境,此处使用Anaconda进行程序集管理,输入以下指令创建一个yolov10环境:

conda create -n yolov10 python=3.9
conda activate yolov10
pip install -r requirements.txt
pip install -e .

3.3 下载模型

  首先导出目标识别模型,此处以官方预训练模型为例,首先下载预训练模型文件,然后调用yolo导出ONBNX格式的模型文件,最后使用 OpenVINO™ 的模型转换命令将模型转为IR格式,依次输入以下指令即可:

wget https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10s.pt
yolo export model=yolov10s.pt format=onnx opset=13 simplify

4. engine模型转换

  首先定义ONNX模型转换Engine格式的代码,如下所示:

#include "opencv2/opencv.hpp"
#include <fstream>
#include <iostream>
#include "cuda.h"
#include "NvInfer.h"
#include "NvOnnxParser.h"

class Logger : public nvinfer1::ILogger
{
    void log(Severity severity, const char* msg) noexcept override
    {
        if (severity <= Severity::kWARNING)
            std::cout << msg << std::endl;
    }
} logger;

void onnxToEngine(const char* onnxFile, int memorySize) {
    // 将路径作为参数传递给函数
    std::string path(onnxFile);
    std::string::size_type iPos = (path.find_last_of('\\') + 1) == 0 ? path.find_last_of('/') + 1 : path.find_last_of('\\') + 1;
    std::string modelPath = path.substr(0, iPos);//获取文件路径
    std::string modelName = path.substr(iPos, path.length() - iPos);//获取带后缀的文件名
    std::string modelName_ = modelName.substr(0, modelName.rfind("."));//获取不带后缀的文件名名
    std::string engineFile = modelPath + modelName_ + ".engine";
    // 构建器,获取cuda内核目录以获取最快的实现
    // 用于创建config、network、engine的其他对象的核心类
    nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);  // 构建器,获取cuda内核目录以获取最快的实现,用于创建config、network、engine的其他对象的核心类
    const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);  // 定义网络属性
    nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);  // 解析onnx网络文件,tensorRT模型类
    nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, logger);   // 将onnx文件解析,并填充rensorRT网络结构
    
    parser->parseFromFile(onnxFile, 2);  // 解析onnx文件
    for (int i = 0; i < parser->getNbErrors(); ++i) {
        std::cout << "load error: " << parser->getError(i)->desc() << std::endl;
    }
    printf("tensorRT load mask onnx model successfully!!!...\n");

    // 创建推理引擎
    nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();  // 创建生成器配置对象。
    config->setMaxWorkspaceSize(1024 * 1024 * memorySize);  // 设置最大工作空间大小。
    config->setFlag(nvinfer1::BuilderFlag::kFP16);  // 设置模型输出精度
    nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);  // 创建推理引擎
    // 将推理文件保存到本地
    std::cout << "try to save engine file now~~~" << std::endl;
    std::ofstream filePtr(engineFile, std::ios::binary);
    if (!filePtr) {
        std::cerr << "could not open plan output file" << std::endl;
        return;
    }
    // 将模型转化为文件流数据
    nvinfer1::IHostMemory* modelStream = engine->serialize();
    // 将文件保存到本地
    filePtr.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
    // 销毁创建的对象
    modelStream->destroy();
    engine->destroy();
    network->destroy();
    parser->destroy();
    std::cout << "convert onnx model to TensorRT engine model successfully!" << std::endl;
}

  通过调用TensorRT模型优化器,可以将ONNX模型进行优化,结合本机GPU设备,进行优化加速,并转换成TensorRT模型支持的模型格式,这一步也可以在模型推理时进行,但是模型优化需要较长时间,因此最好先将模型进行转换。定义好代码后,在主函数中调用即可,如下所示:

onnxToEngine("E:\\Text_Model\\yolov10s.onnx", 50);

5. 定义YOLOv10 Process

5.1 数据预处理

  数据预处理此处通过OpenCV实现,将输入的图片数据转为模型需要的数据情况,代码如下所示:

void preProcess(cv::Mat *img, int length, float* factor, std::vector<float>& data) {
	cv::Mat mat;
    int rh = img->rows;
    int rw = img->cols;
    int rc = img->channels();
	cv::cvtColor(*img, mat, cv::COLOR_BGR2RGB);
    int maxImageLength = rw > rh ? rw : rh;
    cv::Mat maxImage = cv::Mat::zeros(maxImageLength, maxImageLength,CV_8UC3);
    maxImage = maxImage * 255;
    cv::Rect roi (0, 0, rw, rh);
    mat.copyTo(cv::Mat(maxImage, roi));
	cv::Mat resizeImg;
    cv::resize(maxImage, resizeImg, cv::Size(length, length), 0.0f, 0.0f, cv::INTER_LINEAR);
	*factor = (float)((float)maxImageLength / (float)length);
    resizeImg.convertTo(resizeImg, CV_32FC3, 1 / 255.0);
    rh = resizeImg.rows;
    rw = resizeImg.cols;
    rc = resizeImg.channels();
    for (int i = 0; i < rc; ++i) {
        cv::extractChannel(resizeImg, cv::Mat(rh, rw, CV_32FC1, data.data() + i * rh * rw), i);
    }
}

  在调用时也相对简单,将相关变量传入即可,代码如下所示:

Mat frame = new frame();
std::vector<float> inputData(640 * 640 * 3);
float factor = 0;
preProcess(&frame, 640, &factor, inputData);

5.2 结果后处理

  首先此处定义了一个结果类:

struct DetResult {
    cv::Rect bbox;
    float conf;
    int lable;
    DetResult(cv::Rect bbox,float conf,int lable):bbox(bbox),conf(conf),lable(lable){}
};

  然后定义模型的结果处理方式,代码如下所示:

std::vector<DetResult> postProcess(float* result, float factor, int outputLength) {
    std::vector<cv::Rect> positionBoxes;
    std::vector <int> classIds;
    std::vector <float> confidences;
    // Preprocessing output results
    for (int i = 0; i < outputLength; i++)
    {
        int s = 6 * i;
        if ((float)result[s + 4] > 0.2)
        {
            float cx = result[s + 0];
            float cy = result[s + 1];
            float dx = result[s + 2];
            float dy = result[s + 3];
            int x = (int)((cx)* factor);
            int y = (int)((cy)* factor);
            int width = (int)((dx - cx) * factor);
            int height = (int)((dy - cy) * factor);
            cv::Rect box(x, y, width, height);
            positionBoxes.push_back(box);
            classIds.push_back((int)result[s + 5]);
            confidences.push_back((float)result[s + 4]);
        }
    }
    std::vector<DetResult> re;
    for (int i = 0; i < positionBoxes.size(); i++)
    {
        DetResult det(positionBoxes[i], confidences[i], classIds[i]);
        re.push_back(det);
    }
    return re;
}

  最后为了让结果可视化,定义了结果绘制方法,代码如下所示:

void drawBbox(cv::Mat& img, std::vector<DetResult>& res) {
    for (size_t j = 0; j < res.size(); j++) {
        cv::rectangle(img, res[j].bbox, cv::Scalar(255, 0, 255), 2);
        cv::putText(img, std::to_string(res[j].lable) + "-" + std::to_string(res[j].conf), 
            cv::Point(res[j].bbox.x, res[j].bbox.y - 1), cv::FONT_HERSHEY_PLAIN, 
            1.2, cv::Scalar(0, 0, 255), 2);
    }
}

  上述方式调用依旧十分容易,使用代码如下所示:

std::vector<float> output_data(300 * 6);
std::vector<DetResult> result = postProcess(output_data.data(), factor, 300);
drawBbox(frame, result);

6. 模型推理实现

6.1 模型读取与创建推理通道

  首先读取上文中转换的Engine模型,并创建推理通道,用于后文的模型推理,实现代码如下所示:

std::shared_ptr<nvinfer1::IExecutionContext> creatContext(std::string modelPath) {
    // 以二进制方式读取问价
    std::ifstream filePtr(modelPath, std::ios::binary);
    if (!filePtr.good()) {
        std::cerr << "文件无法打开,请确定文件是否可用!" << std::endl;
        return std::shared_ptr<nvinfer1::IExecutionContext>();
    }
    size_t size = 0;
    filePtr.seekg(0, filePtr.end);	// 将读指针从文件末尾开始移动0个字节
    size = filePtr.tellg();	// 返回读指针的位置,此时读指针的位置就是文件的字节数
    filePtr.seekg(0, filePtr.beg);	// 将读指针从文件开头开始移动0个字节
    char* modelStream = new char[size];
    filePtr.read(modelStream, size);
    // 关闭文件
    filePtr.close();
    nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
    nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(modelStream, size);
    return std::shared_ptr<nvinfer1::IExecutionContext>(engine->createExecutionContext());
}

6.2 Yolov10 推理代码

  下面结合一个视频推理,编写TensorRT推理YOLOv10的流程,代码如下所示:

#include "opencv2/opencv.hpp"
#include <fstream>
#include <iostream>
#include "cuda.h"
#include "NvInfer.h"
#include "NvOnnxParser.h"
class Logger : public nvinfer1::ILogger
{
    void log(Severity severity, const char* msg) noexcept override
    {
        // suppress info-level messages
        if (severity <= Severity::kWARNING)
            std::cout << msg << std::endl;
    }
} logger;

void yolov10Infer() {
    const char* videoPath = "E:\\Text_dataset\\car_test.mov";
    const char* enginePath = "E:\\Text_Model\\yolov10s.engine";

    std::shared_ptr<nvinfer1::IExecutionContext> context = creatContext(enginePath);
    cv::VideoCapture capture(videoPath);
    // 检查摄像头是否成功打开
    if (!capture.isOpened()) {
        std::cerr << "ERROR: 视频无法打开" << std::endl;
        return;
    }

    cudaStream_t stream;
    cudaStreamCreate(&stream);

    void* inputSrcDevice;
    void* outputSrcDevice;

    cudaMalloc(&inputSrcDevice, 3 * 640 * 640 * sizeof(float));
    cudaMalloc(&outputSrcDevice, 1 * 300 * 6 * sizeof(float));
    std::vector<float> output_data(300 * 6);
    std::vector<float> inputData(640 * 640 * 3);
    while (true)
    {
        cv::Mat frame;
        if (!capture.read(frame)) {
            break;
        }
        float factor = 0;
    	preProcess(&frame, 640, &factor, inputData);
        cudaMemcpyAsync(inputSrcDevice, inputData.data(), 3 * 640 * 640 * sizeof(float), 
            cudaMemcpyHostToDevice, stream);
        void* bindings[] = { inputSrcDevice, outputSrcDevice };
    	context->enqueueV2((void**)bindings, stream, nullptr);
        cudaMemcpyAsync(output_data.data(), outputSrcDevice, 300 * 6 * sizeof(float),
            cudaMemcpyDeviceToHost, stream);
        cudaStreamSynchronize(stream);
        std::vector<DetResult> result = postProcess(output_data.data(), factor, 300);
        drawBbox(frame, result); 
    	imshow("读取视频", frame);
        cv::waitKey(10);	//延时30
    }
    cv::destroyAllWindows();
}

  通过上诉代码便可以实现使用NVIDIA TensorRT C++部署YOLOv10实现GPU加速。

7. 总结

  在本文中,我们将演示如何使用NVIDIA TensorRT C++ API 部署YOLOv10目标检测模型,实现模型推理加速。最后我们对模型推理速度进行了测试,测试结果如下所示:

PreProcessInferencePostProcess
Time (ms)7.591.356.95

  在上述中已经提供了项目实现的全部源码,但都是零散的,如果大家在使用中有疑问,可以下载项目源码文件,下载链接为:

https://download.csdn.net/download/Grape_yan/89396724

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

个人账号 - 2

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

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

相关文章

C++进阶之二叉搜索树

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 C进阶​ ​​​​算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.二叉搜索树概念 二.实现 2.1插入 …

K8s存储对象的使用

背景和概念 容器中的文件在磁盘上是临时存放的&#xff0c;这给在容器中运行较重要的应用带来一些问题&#xff1a; 当容器崩溃或停止时&#xff0c;此时容器状态未保存&#xff0c; 因此在容器生命周期内创建或修改的所有文件都将丢失。另外 在崩溃期间&#xff0c;kubelet 会…

3D打印随形水路:模具水路的革命性技术

在快速发展的模具制造行业中&#xff0c;3D打印技术以其独特的优势正在引领一场技术革命。其中&#xff0c;3D打印随形水路技术&#xff0c;凭借其灵活性和定制化设计的能力&#xff0c;为模具带来了前所未有的变革。 模具3D打印随形水路技术&#xff0c;是一种利用3D打印技术制…

《帝国时代 III:决定版》秘籍 怎么在苹果电脑上玩《帝国时代 III:决定版》

《帝国时代 III&#xff1a;决定版》是一款让玩家沉浸于历史长河体验从大航海时代到工业革命时期的游戏。下面我们来看看《帝国时代 III&#xff1a;决定版》是什么类型的游戏&#xff0c;《帝国时代 III&#xff1a;决定版》Mac安装教程的相关内容。 一、《帝国时代 III&…

UI设计公司-蓝蓝设计-交通行业ui设计解决方案

来百度APP畅享高清图片 这是北京兰亭妙微科技有限公司&#xff08;简称蓝蓝设计&#xff09;在交通行业的一些ui设计经验&#xff0c;我们建立了UI设计分享群&#xff0c;每天会分享国内外的一些优秀设计&#xff0c;如果有兴趣的话&#xff0c;可以进入一起成长学习&#xff0…

流量录制学习

AREX Cloud | AREX (arextest.com) 流量录制学习&#xff0c;比vivo的moonbox要好用

FineReport使用小记(不断更新中…………)

FineReport使用小记 1. 单元格相关设置1.1. 单元格值样式 2. 报表块设置2.1. 给报表块加单位 1. 单元格相关设置 1.1. 单元格值样式 1. 百分比样式 选中单元格&#xff0c;单元格属性——>文本——>格式——>百分比 下面可以选择保留几位小数&#xff0c;图中为保留…

测试用例如何编写?史上最全接口测试-用例编写设计总结(测试点)

前言 接口测试的流程 接口测试也是属于功能测试&#xff0c;所以跟我们以往的功能测试流程并没有太大区别&#xff0c;测试流程依旧是&#xff1a; 1、测试接口文档&#xff08;需求文档&#xff09; 2、根据接口文档编写测试用例&#xff08;用例编写完全可以按照以往规则来…

查询SQL:文章浏览1

问题描述 请查询出所有浏览过自己文章的作者 结果按照 id 升序排列。 查询结果的格式如下所示&#xff1a; 题目分析&#xff1a; 这题主要考察排序asc&#xff08;升序&#xff09;、desc&#xff08;降序&#xff09;以及distinct字段。 解决方案&#xff1a; select DIS…

033.搜索旋转排序数组

题意 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给方法之前&#xff0c;nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了旋转&#xff0c;使数组变为 [nums[k], nums[k1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]&…

【Python系列】Python 中的运算符:基础与高级用法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

1898java疫情防控管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 疫情防控管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了java设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发…

使用LabVIEW进行大数据数组操作的优化方法

针对大数据量数组操作&#xff0c;传统的内存处理方法可能导致内存不足。通过LabVIEW的图像批处理技术&#xff0c;可以有效地进行大数据数组操作&#xff0c;包括分块处理、并行处理和内存优化等。这种方法能显著提高处理效率和系统稳定性。 图像批处理的优势 内存优化&#…

navi_cat查看数据库的连接密码

Navi_Cat 建立连接&#xff0c;来访问数据库。可惜&#xff0c;忘记了数据库密码&#xff0c;没事&#xff0c;这么搞。 首先先导出链接&#xff0c;再从链接里取出被加密的密码&#xff0c;然后找个可在线运行PHP的网站&#xff08;代码在线运行 - 在线工具&#xff09;&…

ROS云课三分钟外传之CoppeliaSim_Edu_V4_1_0_Ubuntu16_04

三分钟热度试一试吧&#xff0c;走过路过不要错过。 参考之前&#xff1a; 从云课五分钟到一分钟之v-rep_pro_edu_v3_6_2-CSDN博客 git clone https://gitcode.net/ZhangRelay/v-rep_pro_edu_v3_6_2_ubuntu16_04.gittar -xf v-rep_pro_edu_v3_6_2_ubuntu16_04/V-REP_PRO_EDU…

Python爬取城市空气质量数据

Python爬取城市空气质量数据 一、思路分析1、寻找数据接口2、发送请求3、解析数据4、保存数据二、完整代码一、思路分析 目标数据所在的网站是天气后报网站,网址为:www.tianqihoubao.com,需要采集武汉市近十年每天的空气质量数据。先看一下爬取后的数据情况: 1、寻找数据…

网络空间安全数学基础·多项式环与有限域

5.1 多项式环&#xff08;掌握&#xff09; 5.2 多项式剩余类环&#xff08;理解&#xff09; 5.3 有限域&#xff08;熟练&#xff09; 5.1 多项式环 定义&#xff1a;设F是一个域&#xff0c;称是F上的一元多项式&#xff0e; 首项&#xff1a;如果an≠0&#xff0c;则称 a…

618数码产品有什么推荐?四大2024“宝藏”数码产品推荐!

随着618购物节的热情逐渐升温&#xff0c;你是否在繁多且诱人的商品海洋中迷失方向&#xff0c;难以找到那最心仪的宝贝&#xff1f;团团在此特别为你精心挑选了一系列经过亲身体验的优质好物。这些推荐不仅时尚前沿&#xff0c;更贴合你的日常生活需求&#xff0c;确保实用与品…

A6500-LC LVDT 前置器,用于A6500-UM, 导轨安装

电源 22.5V to 32VDC <30mA <0.1%/V <60V( 使用SELV/PELV 供电电源) 约2.2Vrms,5kHz IP20 IEC 60529 -35C to 75C(-31F to 167F) -35C to 85C(-31F to 185F) 电流损耗 供电电压对 运行温度 存储温度 0.35mm(0.014 in ),10 to 55Hz 15g 根据 EN 60068-2-27 根据IEC 613…

Ubuntu server 24 (Linux) sudo 免输密码

1 sudo 使用要输入密码&#xff0c;费时费力。 2 sudo命令免输密码&#xff0c;需要修改/etc/sudoers文件 #本文以test用户为例,#允许不需要输入密码执行 sudo vi /etc/sudoers test ALL(ALL) NOPASSWD: ALL %sudo ALL(ALL:ALL) ALL --> #%sudo ALL(ALL:ALL) ALL#所有…