OrangePi AIpro学习5 —— 模型推理程序开发

news2024/11/15 16:01:51

目录

一、准备工作

1.1 代码裁剪

1.2 测试运行

二、程序讲解

2.1 初始化

2.2 处理模型图片输入

2.3 推理函数

2.4 对输出结果进行处理


前言

本节主要讲解昇腾芯片,例程中使用resnet50推理图像类别的程序。本节讲解的程序,它的环境搭建与使用方法在前面的教程有讲解,第一次看的宝子可以移步前面的教程先搭建一下开发环境

一、准备工作

1.1 代码裁剪

代码目录:cd ~/samples/inference/modelInference/sampleResnetQuickStart

下图,对原来的代码进行备份,然后压缩一下代码量

#include <opencv2/opencv.hpp>
#include <dirent.h>
#include "acl/acl.h"
#include "label.h"

using namespace cv;
using namespace std;

class SampleResnetQuickStart {
public:
    SampleResnetQuickStart(const char* ModelPath, int32_t modelWidth, int32_t modelHeight);
    void InitResource();
    void ProcessInput(const string testImgPath);
    void Inference();
    void GetResult();
private:
    aclrtContext context_;
    aclrtStream stream_;
    uint32_t modelId_;
    const char * modelPath_;
    int32_t modelWidth_;
    int32_t modelHeight_;
    aclmdlDesc * modelDesc_;
    aclmdlDataset * inputDataset_;
    aclmdlDataset * outputDataset_;
    void * inputBuffer_;
    void * outputBuffer_;
    size_t inputBufferSize_;
    float * imageBytes;
    String imagePath;
    Mat srcImage;
    aclrtRunMode runMode_;
};

SampleResnetQuickStart::SampleResnetQuickStart(const char* modelPath, int32_t modelWidth, int32_t modelHeight)
{
    modelPath_ = modelPath;
    modelWidth_ = modelWidth;
    modelHeight_ = modelHeight;
}

void SampleResnetQuickStart::InitResource()
{
    aclInit("");                                // AscendCL初始化函数
    aclrtSetDevice(0);                          // 设置本进程使用哪一个昇腾芯片
    aclrtCreateContext(&context_, 0);           // 在昇腾芯片上面创建一个集合,指定当前线程使用这个集合,传出集合的context(句柄)
    aclrtCreateStream(&stream_);                // 在集合中创建一个用来处理任务的stream(一个昇腾芯片可以创建几百上千的stream)
    aclrtGetRunMode(&runMode_);

    aclmdlLoadFromFile(modelPath_, &modelId_);  // 加载一个模型,传出这个模型的modelId(句柄)
    modelDesc_ = aclmdlCreateDesc();            // 创建模型描述符
    aclmdlGetDesc(modelDesc_, modelId_);        // 从模型modelId(句柄)中获取模型信息到模型描述符中

    // 分配模型输入数据空间
    inputDataset_ = aclmdlCreateDataset();              // 获取模型推理时传入的结构体
    inputBufferSize_ = aclmdlGetInputSizeByIndex(modelDesc_, 0);    
                                                        // 通过模型描述符,获取模型第1个输入需要的空间(模型可能有多个输入)
    aclrtMalloc(&inputBuffer_, inputBufferSize_, ACL_MEM_MALLOC_HUGE_FIRST);
    aclDataBuffer * inputData = aclCreateDataBuffer(inputBuffer_, inputBufferSize_);
    aclmdlAddDatasetBuffer(inputDataset_, inputData);   // 把分配的空间大小信息和地址指针,给模型推理时传入的结构体

    // 分配模型输出数据空间
    outputDataset_ = aclmdlCreateDataset();             // 获取模型推理时传出的结构体
    size_t modelOutputSize = aclmdlGetOutputSizeByIndex(modelDesc_, 0);     
                                                        // 通过模型描述符,获取模型第1个输出需要的空间(模型可能有多个输入)
    aclrtMalloc(&outputBuffer_, modelOutputSize, ACL_MEM_MALLOC_HUGE_FIRST);
    cout << modelOutputSize << endl;
    aclDataBuffer * outputData = aclCreateDataBuffer(outputBuffer_, modelOutputSize);
    aclmdlAddDatasetBuffer(outputDataset_, outputData); // 把分配的空间大小信息和地址指针,给模型推理时传出的结构体
}

void SampleResnetQuickStart::ProcessInput(const string testImgPath)
{
    // read image from file by cv
    imagePath = testImgPath;
    srcImage = imread(testImgPath);

    // zoom image to modelWidth_ * modelHeight_
    Mat resizedImage;
    resize(srcImage, resizedImage, Size(modelWidth_, modelHeight_));

    // get properties of image
    int32_t channel = resizedImage.channels();
    int32_t resizeHeight = resizedImage.rows;
    int32_t resizeWeight = resizedImage.cols;

    // data standardization
    const float min_chn_0 = 123.675;
    const float min_chn_1 = 116.28;
    const float min_chn_2 = 103.53;
    const float var_reci_chn_0 = 0.0171247538316637;
    const float var_reci_chn_1 = 0.0175070028011204;
    const float var_reci_chn_2 = 0.0174291938997821;
    float meanRgb[3] = {min_chn_2, min_chn_1, min_chn_0};
    float stdRgb[3]  = {var_reci_chn_2, var_reci_chn_1, var_reci_chn_0};

    // create malloc of image, which is shape with NCHW
    imageBytes = (float*)malloc(channel * resizeHeight * resizeWeight * sizeof(float));
    memset(imageBytes, 0, channel * resizeHeight * resizeWeight * sizeof(float));

    uint8_t bgrToRgb=2;
    // image to bytes with shape HWC to CHW, and switch channel BGR to RGB
    for (int c = 0; c < channel; ++c)
    {
        for (int h = 0; h < resizeHeight; ++h)
        {
            for (int w = 0; w < resizeWeight; ++w)
            {
                int dstIdx = (bgrToRgb - c) * resizeHeight * resizeWeight + h * resizeWeight + w;
                imageBytes[dstIdx] =  static_cast<float>((resizedImage.at<cv::Vec3b>(h, w)[c] - 1.0f*meanRgb[c]) * 1.0f*stdRgb[c] );
            }
        }
    }
}

void SampleResnetQuickStart::Inference()
{
    // 拷贝host数据到device中
    aclrtMemcpyKind kind;
    if (runMode_ == ACL_DEVICE)
    {
        kind = ACL_MEMCPY_DEVICE_TO_HOST;
    }
    else{
        kind = ACL_MEMCPY_HOST_TO_DEVICE;
    }

    // 把opencv处理好的数据放入输入缓冲区中
    aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);

    // 执行推理
    aclmdlExecute(modelId_, inputDataset_, outputDataset_);
}

void SampleResnetQuickStart::GetResult() 
{
    // get result from output data set
    void * outHostData = nullptr;
    float * outData = nullptr;
    size_t outputIndex = 0;
    aclDataBuffer * dataBuffer = aclmdlGetDatasetBuffer(outputDataset_, outputIndex);
    void * data = aclGetDataBufferAddr(dataBuffer);
    uint32_t len = aclGetDataBufferSizeV2(dataBuffer);

    // copy device output data to host
    aclrtMemcpyKind kind;
    if (runMode_ == ACL_DEVICE)
    {
        kind = ACL_MEMCPY_DEVICE_TO_HOST;
    }
    else
    {
        kind = ACL_MEMCPY_HOST_TO_DEVICE;
    }
    aclrtMallocHost(&outHostData, len);
    aclrtMemcpy(outHostData, len, data, len, kind);

    outData = reinterpret_cast<float*>(outHostData);

    // create map<confidence, class> and sorted by maximum
    map<float, unsigned int, greater<float>> resultMap;
    for (unsigned int j = 0; j < len / sizeof(float); ++j) 
    {
        resultMap[*outData] = j;
        outData++;
    }

    // do data processing with softmax and print top 1 classes
    double totalValue=0.0;
    for (auto it = resultMap.begin(); it != resultMap.end(); ++it) 
    {
        totalValue += exp(it->first);
    }

    // get max <confidence, class>
    float confidence = resultMap.begin()->first;
    unsigned int index = resultMap.begin()->second;
    string line = format("label:%d  conf:%lf  class:%s", index, exp(confidence) / totalValue, label[index].c_str());

    // write image to ../out/
    cv::putText(srcImage, line, Point(0,35), cv::FONT_HERSHEY_TRIPLEX, 1.0, Scalar(255,0,0),2);

    int sepIndex = imagePath.find_last_of("/");
    string fileName = imagePath.substr(sepIndex + 1, -1);
    string outputName = "out_" + fileName;
    imwrite(outputName, srcImage);
    cout << outputName << endl;
    cout << line << endl;

    aclrtFreeHost(outHostData);
    outHostData = nullptr;
    outData = nullptr;
}

int main()
{
    const char * modelPath = "../model/resnet50.om";
    int32_t modelWidth = 224;
    int32_t modelHeight = 224;

    vector<string> allPath;
    allPath.push_back("../data/dog1_1024_683.jpg");

    SampleResnetQuickStart sampleResnet(modelPath, modelWidth, modelHeight);
    sampleResnet.InitResource();

    for (size_t i = 0; i < allPath.size(); i++)
    {
        sampleResnet.ProcessInput(allPath.at(i));
        sampleResnet.Inference();
        sampleResnet.GetResult();
    }
    return 0;
}

1.2 测试运行

如果报错,解决方法可以看前面的教程。下图是推理成功了,类别是 label:162,名字是class:beagle,概率是conf:0.902209

数据集所有类别: 

ImageNet图像库1000个类别名称(中文注释不断更新)_imagene162t类别-CSDN博客

二、程序讲解

2.1 初始化

204行,SampleResnetQuickStart sampleResnet(modelPath, modelWidth, modelHeight);

这是在程序中自定义的一个类,这个类是用来进行流程控制的。初始化类的时候,将模型和模型要求输入的图片宽高输入进去了

这个模型的宽高是根据模型来确定的,resnet50要求输入224*224*3的矩阵,代表图片宽是224,高是224,有RGB三个颜色通道

205行,sampleResnet.InitResource();

在模型参数传进去后,SampleResnetQuickStart类会根据传入的模型文件、模型宽高来对模型推理需要的参数进行初始化

44行,aclInit("");

● 只有执行了这一行,才能使用后续的acl库函数,一个进程只执行一次

● 在进程的最后,要成对的使用 aclFinalize(); 函数,做收尾工作

45行,clrtSetDevice(0); 

● 一个主板上有多个昇腾芯片(主板上插了多块显卡),选择索引号为0的昇腾芯片(选择第一块显卡)

● 在线程的最后,要成对的使用aclrtResetDevice(0);函数,做收尾工作

46行,aclrtCreateContext(&context_, 0);

● 香橙派上是昇腾310B芯片,昇腾310B芯片device中默认存在1个context,本程序中可创建也可不创建context

● 猜测context可能不是一个实体的内容,而是stream集合的一个概念,几个stream组成一个context

● 本程序就是在昇腾设备0上创建了一个context。创建好了以后,本线程就默认使用新创建的context。当前线程在同一时刻内只能使用其中一个Context

●  在线程的最后,要成对的使用aclrtDestroyContext(g_context);函数,做收尾工作

47行:aclrtCreateStream(&stream_);

● 用于给当前context创建一个stream。上图昇腾310B芯片硬件资源最多支持1024个stream

● 一个stream可以用于执行一个任务,本案例中使用这个stream执行了图片裁剪指定区域的任务。多个stream可以用于同时执行多个任务

● 在线程的最后,要成对的使用aclrtDestroyStream(g_stream);函数,做收尾工作

48行:aclrtGetRunMode(&runMode_);

● 获取当前程序的运行模式

ACL_DEVICE:昇腾AI软件栈运行在Device的Control CPU或板端环境上
ACL_HOST:昇腾AI软件栈运行在Host CPU上

如果是ACL_DEVICE,就代表当前程序是在AI CPU上运行的;如果是ACL_HOST,就代表当前程序是在CPU上运行的,如下图红色圆圈中写的那样

50行,aclmdlLoadFromFile(modelPath_, &modelId_);

把硬盘里面的模型,加载到内存条上。因为可能加载了多个不同的模型到内存条上了,所以要通过modelId_(句柄)去找到modelPath("../model/resnet50.om")模型在内存条上加载的位置。

51-52行,modelDesc_ = aclmdlCreateDesc();  aclmdlGetDesc(modelDesc_, modelId_);

创建一个模型的描述符,类似于创建了一个结构体,如下图:

struct ModelDesc {
    vector<int> allBufferSize;
    ...
};

strcut ModelDesc * modelDesc_ = new ModelDesc;

然后aclmdlGetDesc(modelDesc_, modelId_);就是根据模型的句柄,在内存上找到模型,将模型的各种信息赋值给上面的结构体

55行,inputDataset_ = aclmdlCreateDataset();

创建一个输入数据描述符,类似于创建一个结构体,如下图:

struct Dataset {
    uint32_t bufLen;
    char * bufData;
    ...
};

struct Dataset * inputDataset_ = new Dataset;

56行,inputBufferSize_ = aclmdlGetInputSizeByIndex(modelDesc_, 0);

这段代码相当于获取结构体ModelDesc的vector<int> allBufferSize;中第一个值。

allBufferSize中存储的是模型从第1个到第N个输入需要的空间大小(单位是字节),resnet50这个模型只有1个输入,所以现在只能取到索引号为0的输入

58行,aclrtMalloc(&inputBuffer_, inputBufferSize_, ACL_MEM_MALLOC_HUGE_FIRST);

根据模型输入需要的大小,在显存上分配空间,也就是下图昇腾310B芯片中的内存上分配空间

59-60行,aclDataBuffer * inputData = aclCreateDataBuffer(inputBuffer_, inputBufferSize_); aclmdlAddDatasetBuffer(inputDataset_, inputData);

相当于给结构体struct Dataset中的数据赋值。inputDataset_->bufLen=inputBufferSize_; inputDataset_->bufData=inputBuffer_;

输出结构体的代码逻辑和输入是一样的,这里不多赘述了,至本段代码结束,我们得到了下面三个关键的变量,其中inputDataset_->bufData和inputBuffer_同时指向大小为602112字节的内存空间

outputBuffer_->bufData指向大小为4000字节的内存空间

2.2 处理模型图片输入

下图209行,就是处理模型输入图片的,使用opencv方法将图片resize成224*224*3格式的矩阵,然后将图片的数据给到602112字节的输入数组中(在上图中)

下图210行,对图片进行推理,得到1000*4的输出矩阵,输出矩阵的数据存放到4000字节的输出数组中(在上图)

下图211行,对输出矩阵中的内容进行处理

下面详细的看一下ProcessInput函数

76行,srcImage = imread(testImgPath);

使用opencv,读取图片到内存中

80行,resize(srcImage, resizedImage, Size(modelWidth_, modelHeight_));

将图片大的宽高修改为224*224

83-85行,获取图片的通道数为3,RGB。图片的宽高为224*224

88-113行

● 对图片进行处理,将图片从HWC转成CHW。

HWC格式是 [224, 224, 3] 类型的矩阵,相当于有一个二维数组arr[224][224],数组的元素是那个位置的RGB,符合我们的直观感觉

CHW格式是 [3, 224, 224] 类型的矩阵,相当于有一个一维数组arr[3],这个数组的元素是一个二维数组,arr[0]、arr[1]、arr[2]中的存储的分别是RGB三个通道在224*224图片每个位置的颜色值

● 将图片从BGR转换成RGB。如果是HWC,则arr[3][2](随机的一个位置)的变化如下图

如果是CHW,则变化则如下,下图从上面的BGR变成RGB。下图中,每个方格里面存储的是1字节数据,区别于上图一个方格里面存储的是RGB 3个字节数据

● ProcessInput(const string testImgPath)这个函数会将得到的数据存储到char * imageBytes[602112]; 字节数组中

2.3 推理函数

129行,aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);

如下图,前面处理模型图片输入函数已经将读取到 并且处理好的图片放到imageBytes数组中了,129行代码的作用就是将imageBytes的数据拷贝到inputBuffer_数组中去

● 132行,aclmdlExecute(modelId_, inputDataset_, outputDataset_);

函数根据模型的id,获取到模型信息。根据输入图片获取到输入图片的数据,然后进行推理。将推理得到的数据存放到outputDataset_,等下就可以通过outputDataset_来获取输出的推理结果

2.4 对输出结果进行处理

141-143行,通过推理得到的outputDataset_获取其中的数据。得到的数据存储在void * data;数组中,将这个数组强转成float * outData;数组。得到的长度len应该是4000字节

161-166行,float的长度是4,所以最终float数组的长度是1000,利用map对float数组进行排序,得到float数组中最大的数字。

这个数字就是程序最终的预测概率,又通过这个最大的数字在数组中的位置,找到这个类别。例如本程序 outData[162] 是最大的,并且通过outData[162] 计算出预测准确率是90.2209%

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

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

相关文章

考PMP需要多久?一篇文章告诉你如何省心备考PMP

PMP备考需要半年时间吗&#xff1f;....是要满分拿下PMP吗&#xff1f; PMP备考不用半年时间&#xff0c;如果你想的话&#xff0c;可以一个多月就成功上岸哦。如果你不想的话&#xff0c;也可以半年。 其实大家认真去学习的&#xff0c;时间根本不需要那么长的时间&#xff0…

全新Bty分销系统源码v1.0/宝塔分销系统开源版源码/独立后台(附安装教程)

源码简介&#xff1a; 全新Bty分销系统源码v1.0&#xff0c;它作为宝塔分销系统开源版源码&#xff0c;功能强大&#xff0c;它内置了易支付功能&#xff0c;方便多了&#xff01; 这个Bty分销系统开源版&#xff0c;宝塔分销系统开源版。它基于宝塔开放的API底层控制器&…

LoadRunner12添加mysql数据连接驱动

参考链接 lr进行mysql参数化 https://jingyan.baidu.com/article/6d704a13407c4128db51ca2d.html 下载mysql数据库驱动教程 Loadrunner12使用MYSQL5.0参数化数据遇到的问题_loadrunner12 参数化没有效果-CSDN博客 mysql数据驱动下载链接 MySQL :: Download MySQL Connect…

centos 下如何安装openjdk21

文章目录 前言一、下载OpenJDK二、上传OpenJdk三、解压四、编辑环境变量五、重新加载一下配置六、验证是否安装成功 前言 本文章是自己将openjdk下载好&#xff01;手动上传解压的方式进行安装&#xff01; 一、下载OpenJDK OpenJdk官网&#xff1a;点击访问 二、上传OpenJ…

封装Form表单【后台控制表单搜索项的显隐和排序】

概要 为了实现需求&#xff1a;后台控制表单搜索项的显隐和排序&#xff1b; 整体思路流程 表单搜索项统一配置&#xff0c;封装成一个组件&#xff0c;把不同类别再封装成单个的组件&#xff0c;配置项数组由前端控制&#xff08;暂由前端配置&#xff0c;这样虽然代码量多&…

央行重提P2P存量业务化解,非吸案开始翻旧账?

沉寂已久的P2P&#xff0c;又突然以另一种意想不到的形式回到公众视野了。2018年全国P2P坍塌式暴雷&#xff0c;平台老板“跑路”“判刑”的消息一时间你方唱罢我登场。当年的某凰金融、某租宝、某信贷等赫赫有名的网贷平台传出的消息无非两类——查封或跑路&#xff0c;这几年…

外卖O2O系统开发源码开源介绍

外卖O2O系统开发源码开源介绍 开源外卖O2O系统源码可以为开发者提供快速搭建外卖平台的基础&#xff0c;节省从零开始的开发时间。 以下是几个推荐的开源项目&#xff1a; flash-waimai 是一个基于Spring Boot和Vue.js的前后端分离的外卖系统&#xff0c;包含手机端和后台管理…

代码随想录算法训练营第三十五天|背包问题理论基础、携带研究材料、分割等和子集

背包问题理论基础 1.背包问题概述 01背包&#xff1a;有n种物品&#xff0c;每种物品只有一个&#xff1b; 完全背包&#xff1a;有n种物品&#xff0c;每种物品有无限个&#xff1b; 多重背包&#xff1a;有n种物品&#xff0c;每种物品的个数各不相同。 2. 01背包 有n件…

旋转目标数据集制作:roLabelImg的安装和使用

目录 创建roLabelImg环境 安装pyqt5和lxml 下载roLabelImg源码包 使用roLabelImg roLabelImg常用操作指令 标注展示 由于最近一些项目需要标注旋转数据集&#xff0c;在网上找了一些教程&#xff0c;但大多数都显得比较杂乱&#xff0c;因此想把这些重新整理一下&#xf…

汽车免拆诊断案例 | 2013款北京现代悦动车发动机偶尔无法起动

故障现象 一辆2013款北京现代悦动车&#xff0c;搭载G4FC发动机&#xff0c;累计行驶里程约为13.9万km。车主反映&#xff0c;发动机偶尔无法起动着机&#xff0c;断开点火开关&#xff0c;等待一会儿又可以起动着机。 故障诊断 接车后反复试车&#xff0c;当发动机无法起动着…

TS RadiMation®软件EUT监测与控制:抗扰度测试的智能解决方案

随着电子设备在各个领域的广泛应用&#xff0c;确保它们在各种电磁环境中可靠运行变得尤为重要。TS RadiMation软件以其卓越的EUT监测与控制功能&#xff0c;为抗扰度测试提供了一站式智能解决方案。 在本文中&#xff0c;我们将深入探讨TS RadiMation如何通过先进的输入通道配…

【MATLAB第108期】基于MATLAB的fast、vbsa、dynia、eet、glue、pawn、rsa敏感性分析模型合集(无目标函数)【更新中】

【MATLAB第108期】基于MATLAB的fast、vbsa、dynia、eet、glue、pawn、rsa敏感性分析模型合集&#xff08;无目标函数&#xff09;【更新中】 一、FAST&#xff08;Fourier Amplitude Sensitivity Test&#xff09; FAST&#xff08;Fourier Amplitude Sensitivity Test&#…

2024年10大最佳研发工时管理系统推荐

这篇文章介绍了以下几个工具&#xff1a;PingCode、Worktile、无鱼项目工时系统、盖雅工厂、泽众ALM、蓝凌KMS、Forecast、EasyRedmine、Trello、Hubstaff。 在选择研发工时管理系统时&#xff0c;很多人都感到无从下手。市面上的工具五花八门&#xff0c;功能和特点各不相同&a…

专题十四_优先级队列

目录 1046. 最后一块石头的重量 解析 题解 703. 数据流中的第 K 大元素 解析 题解 692. 前K个高频单词 解析 题解 1046. 最后一块石头的重量 1046. 最后一块石头的重量 解析 题解 class Solution { public:int lastStoneWeight(vector<int>& stones) {// 专…

idea 对于mybatis-plus框架JRebelX和XRebel热启动失效问题

1.mybatis-plus不需要使用热启动插件&#xff0c;修改完代码后&#xff0c;直接重新编译一下即可&#xff0c;不需要重启 2.如果是mapper.xml文件&#xff0c;则直接安装JRebel MybatisPlus extension 插件即可完成mapper.xml静态文件更改进行热加载

墨水屏显示颜色过程中的问题,数据和像素值提取比较

软件使用步骤参考 数据数量问题 对于一个单层图片来说&#xff0c;可以分辨率可以使用像素的数量来描述。图片的长宽由多少像素组成就是所说的图片的长宽。这种说法也不太准确&#xff0c;一般人为分辨率越大&#xff0c;约清晰。这种认知是在同样长度中有更多像素&#xff0…

计算机毕业设计 助农产品采购平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

HDFS 原理和操作

目录 一、操作1. web工具2.命令行----常用命令3.Java APIJavaAPI创建HDFS目录&#xff0c;获取HDFS修改权限JavaAPI上传和下载数据使用JavaAPI获取HDFS元信息 二、HDFS原理解析1.数据上传2.数据下载 三、HDFS的高级特性1.回收站2.快照3.配额Quota4.安全模式5.权限管理命令行Jav…

Linux系统之部署俄罗斯方块网页小游戏(二)

Linux系统之部署俄罗斯方块网页小游戏(二) 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看…

动态规划(二)——例题

目录 Help Jimmy 题目 解题思路 神奇的口袋 题目 枚举的解法 递归的解法 动态规划的解法 滑雪 题目 解题思路 解法一 解法二 Help Jimmy 题目 "Help Jimmy" 是在下图所示的场景上完成的游戏&#xff1a; 场景中包括多个长度和高度各不相同的平台。地面是…