【OrangePi AIpro】从开箱到第一个AI应用开发

第一章 OrangePi AIpro介绍和开发环境搭建

1.1 OrangePi AIpro介绍

OrangePi AIpro(8T)采用昇腾AI技术路线,具体为4核64位处理器+AI处理器,集成图形处理器,支持8TOPS AI算力,拥有8GB/16GB LPDDR4X,可以外接32GB/64GB/128GB/256GB eMMC模块,支持双4K高清输出。Orange Pi AIpro支持Ubuntu、openEuler操作系统,满足大多数AI算法原型验证、推理应用开发的需求。

这块板子的特色在于香橙派 X 华为昇腾,可以接入华为昇腾的生态,基于Ascend的CANN架构、AI框架、开发工具链等进行相关应用开发。(总结:作为业界首款基于昇腾深度研发的AI开发板,OrangePi AIpro无论在外观上、性能上还是技术服务支持上都非常优秀)。

1、在VMware Workstation 16安装Ubuntu 22.04系统

① 在Ubuntu官网下载下载Ubuntu 22.04.4 LTS

② 打开安装VMware Workstation 16 Player安装的VMware软件,选择“创建新虚拟机”。

③ 选择①中下载的镜像文件,单击“下一步”。选择虚拟机安装位置。

指定Ubuntu可以使用的内存和处理器核数,修改为16G内存与8核处理器。

指定磁盘容量,至少分配50GB,选择“将虚拟磁盘拆分成多个文件”,单击“下一步”。

④ 其他默认即可,配置完成后单击“下一步”即可进入虚拟机开始系统安装。

⑤ 完成Ubuntu 22.04系统安装

2、在正式安装CANN前先进行相关依赖

① 更换下载源(使用阿里云)

执行如下命令检查源是否可用。如果命令执行报错或者后续安装依赖时等待时间过长甚至报错,则检查网络是否连接或者把“/etc/apt/sources.list”文件中的源更换为可用的源或使用镜像源。

sudo apt-get update
# pip永久换源
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ 
pip config set global.trusted-host mirrors.aliyun.com

tips:也可以用华为镜像,具体参考官网。

② 检查用户的umask

lhh@virtual:~$ umask
0022

如果umask不等于0022,在.bashrc文件的最后一行添加umask 0022后保存

sudo nano ~/.bashrc 
// ctrl+o保存,ctrl+x退出编辑器
source ~/.bashrc

② 安装依赖

sudo apt-get install -y gcc g++ make cmake zlib1g zlib1g-dev openssl \\ 
libsqlite3-dev libssl-dev libffi-dev libbz2-dev libxslt1-dev unzip \\ 
pciutils net-tools libblas-dev gfortran libblas3

安装pip3

sudo apt-get install python3-pip

③ 安装python开发相关依赖包

pip3 install attrs
pip3 install numpy
pip3 install decorator
pip3 install sympy
pip3 install cffi
pip3 install pyyaml
pip3 install pathlib2
pip3 install psutil
pip3 install protobuf
pip3 install scipy
pip3 install requests
pip3 install absl-py

至此,完成依赖包的安装,接下来就是CANN的正式编译安装了。

官方tips:依赖安装完成后,请用户恢复为原umask值(删除.bashrc文件中umask 0022一行)。基于安全考虑,建议用户将umask值改为0027

3、编译安装CANN ① 下载CANN包 在官网查看并下载CANN软件“Ascend-cann-toolkit_{version}_linux-x86_64.run” (快速链接)

② 在终端中进入安装包所在目录,增加软件包的可执行权限后进行安装

chmod +x Ascend-cann-toolkit_6.2.RC2_linux-x86_64.run
./Ascend-cann-toolkit_6.2.RC2_linux-x86_64.run --install

安装完成后,若显示如下信息,则说明软件安装成功:

[INFO] xxx install success

③ 在~/.bashrc中配置开发套件包的环境变量,方便随地可调用CANN相关指令(如atc)。

sudo nano ~/.bashrc

在~/.bashrc最后添加以下。注意与安装路径匹配。

. /home/lhh/Ascend/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/home/lhh/Ascend/ascend-toolkit/latest/x86_64-linux/devlib/:$LD_LIBRARY_PATH
export DDK_PATH=/home/lhh/Ascend/ascend-toolkit/latest 
export NPU_HOST_LIB=$DDK_PATH/runtime/lib64/stub

使环境配置生效,这样就不需要反复去配置环境了。

source nano ~/.bashrc

3、测试模型转换工具ATC 昇腾张量编译器(Ascend Tensor Compiler,简称ATC)是异构计算架构CANN体系下的模型转换工具, 它可以将开源框架的网络模型(如Caffe、TensorFlow、ONNX、MindSpore)以及Ascend IR定义的单算子描述文件(json格式)转换为昇腾AI处理器支持的.om格式离线模型。

ATC的工作流程大致如下: stage1:网络模型经过Parser解析后,转换为中间态IR Graph。 stage2:中间态IR经过图准备、图拆分、图优化、图编译等一系列操作后,转成适配昇腾AI处理器的离线模型.om模型。 stage3:转换后的离线模型上传到板端环境,通过AscendCL接口加载模型文件实现推理过程。

至于为什么要大费周章在虚拟机上安装CANN环境,反正板子有环境直接在板子上做模型转换不就行了吗,[狗头]因为板子性能终归不及PC端,实测板端模型转换十分耗时,在Linux虚拟机上进行模型转换视模型大小也需要一到几分钟不等的时间。

以下以ONNX模型转为om模型为例测试ATC工具是否正常工作。 ① 以我以mobilenet_v2图像分类的预训练模型.onnx文件为例,进入模型文件所在位置。通过onnx可视化可以看到模型的输入为1x1x224x224,且输入input_name即为input

② 执行如下命令生成离线模型,atc有众多参数可选择,但是有必须使用的参数介绍如下表所示。ATC说明文档

atc --model=mobilenet_v2.onnx --framework=5 --output=mobilenet_v2 --soc_version=Ascend310B4 

参数

说明

model

原始模型文件,填写模型文件时需要带上格式,如.onnx

weight

原始模型权重,该参数在转换Caffe模型场景下使用。

framework

0:Caffe; 1:MindSpore; 3:Tensorflow; 5:ONNX

output

保存转换后的om离线推理模型文件路径。

soc_version

处理器型号,填写“Ascend310B4”。

③ 输出以下info则模型转换成功,得到mobilenet_v2.om文件以及fusion_result.json文件。

ATC start working now, please wait for a moment.
... 
ATC run success, welcome to the next use.

第二章 开箱和板端环境搭建

拿到手时首先OrangePi AIpro这个板子是比之前用过的树莓派、香橙派要稍大一些的,还配备了TypeC接口的20V PD-65W适配器散热器。官方已经贴心地提前烧录好了opiaipro ubuntu22.04系统(内部已装好CANN,如果没有预装系统的话,可以查看官方镜像源)。如果自行编译安装,这里面需要注意的是虚拟机上的CANN安装包是不可以直接拿来用的,需要下载安装Ascend-cann-toolkit_7.0.0_linux-aarch64.run,具体的话可以通过官方百度云盘或者昇腾官方资源下载。

2.1 板端ubuntu系统烧录

根据官方给的Linux镜像烧录工具-balenEther进行系统镜像烧录,镜像也为官方提供的opiaipro_ubuntu22.04_desktop_aarch64_20240318.img

2.2 板端编译安装CANN框架--

① 下载CANN包 查看并下载CANN软件“Ascend-cann-toolkit_{version}_linux-aarch64.run” (香橙派资源快速链接、昇腾官方资源)

② 在终端中进入安装包所在目录,增加软件包的可执行权限后进行安装

chmod +x Ascend-cann-toolkit_7.0.0_linux-aarch64.run
sudo ./Ascend-cann-toolkit_7.0.0_linux-aarch64.run --install

安装完成后,若显示如下信息,则说明软件安装成功:

Toolkit:  Ascend-cann-toolkit_7.0.0_linux-aarch64 install success, installed in /home/HwHiAiUser/Ascend.

③ 在~/.bashrc中配置开发套件包的环境变量,方便随地可调用CANN相关指令(如atc)。

sudo nano ~/.bashrc

在~/.bashrc最后添加以下。注意与安装路径匹配。

. /home/HwHiAiUser/Ascend/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/home/HwHiAiUser/Ascend/ascend-toolkit/latest/aarch64-linux/devlib/:$LD_LIBRARY_PATH
export DDK_PATH=/home/HwHiAiUser/Ascend/ascend-toolkit/latest/
export NPU_HOST_LIB=$DDK_PATH/runtime/lib64/stub

使环境配置生效,这样就不需要反复去配置环境了。

source ~/.bashrc

2.3 测试Ubuntu系统和CANN是否正常运行

在终端窗口,执行cd /opt/opi_test/ResnetPicture命令,进入样例目录,运行预训练的ResnetPicture推理进行图像分类,该样例是基于PyTorch框架的ResNet50模型,对jpg图片分类,并在终端显示该图片的Top5置信度的分类ID、分类名称。

su root
cd /opt/opi_test/ResnetPicture/scripts
bash sample_run.sh

第三章 编译构建第一个推理样例:图片分类

前面我们已经通过系统自带的demo测试了CANN的可用性,那么如何去搭建这么一个图像分类推理框架呢。

3.1 在虚拟机转换得到.om模型

获取PyTorch框架的ResNet50模型(resnet50.onnx ),并转换为昇腾AI处理器能识别的模型(resnet50.om)。可以用npu-smi info查看板子的信息,即soc版本,OrangePi AIpro是Ascend310B4

wget https://obs-9be7.obs.cn-east-2.myhuaweicloud.com/003_Atc_Models/resnet50/resnet50.onnx 
atc --model=resnet50.onnx --framework=5 --output=resnet50 --input_shape="actual_input_1:1,3,224,224"  --soc_version=Ascend310B4

3.2 CLion远程开发

我一般采用CLion+SSH远程连接板子,采用远程文件映射和编译进行应用开发,CLion的配置和使用可以参考我之前的一篇博客(CLion+Opencv+QT开发相关)中的CLion安装配置章节。

1、新建工程

首先是在CMakeLists.txt上链接相关库,如我这里会用到Ascend和Opencv,因此分别配置如下:

配置Ascend,root安装默认以下路径/usr/local/Ascend/ascend-toolkit,如果你是非root安装的话就需要自行根据安装的位置进行修改。

// 配置Ascend,root安装默认以下路径
set(INC_PATH $ENV{DDK_PATH})
if (NOT DEFINED ENV{DDK_PATH})
    set(INC_PATH "/usr/local/Ascend/ascend-toolkit/latest")
    message(STATUS "set default INC_PATH: ${INC_PATH}")
else()
    message(STATUS "set INC_PATH: ${INC_PATH}")
endif ()

set(LIB_PATH $ENV{NPU_HOST_LIB})
if (NOT DEFINED ENV{NPU_HOST_LIB})
    set(LIB_PATH "/usr/local/Ascend/ascend-toolkit/latest/runtime/lib64/stub")
    message(STATUS "set default LIB_PATH: ${LIB_PATH}")
else()
    message(STATUS "set LIB_PATH: ${LIB_PATH}")
endif ()

include_directories(
        ${INC_PATH}/runtime/include/
)
link_directories(
        ${LIB_PATH}
)

链接Opencv,板子上的opencv版本是4.5.4,可以通过pkg-config --modversion opencv4命令来查看opencv4版本,如果你是装的opencv2/3的话,用pkg-config opencv --modversion查看版本信息。

# OpenCV
find_package(OpenCV 4.5.4 REQUIRED)
find_package(OpenCV COMPONENTS core highgui imgproc imgcodecs)

链接工程需要的所有包

target_link_libraries(testinf // project_name
                  ascendcl 
                  acl_dvpp 
                  acllite_dvpp_lite 
                  acllite_om_execute 
                  acllite_common 
                  stdc++ 
                  dl 
                  rt 
                  ${OpenCV_LIBS}
)

2、编辑main.cpp,实现图片读取、NPU资源初始化、模型加载和推理、资源释放等功能。

  • 构造ResnetCls类实现初始化资源、图片数据预处理、模型推理、推理后处理、释放资源等功能。

class ResnetCls {
public:
    ResnetCls (int32_t device, const char* ModelPath, int32_t modelWidth, int32_t modelHeight);
    ~ResnetCls();
    Result InitResource();  // 初始化资源
    Result ProcessInput(const string testImgPath);  // 处理输入图片
    Result Inference(); // 模型推理
    Result GetResult(); // 得到推理结果
private:
    void ReleaseResource();  // 释放资源
    int32_t deviceId_;       // 设备编号
    aclrtContext context_;   // context
    aclrtStream stream_;     // stream
    uint32_t modelId_;       // 模型编号
    const char* modelPath_;  // 模型路径
    int32_t modelWidth_;     // 输入模型图片width
    int32_t modelHeight_;    // 输入模型图片height
    aclmdlDesc *modelDesc_;  // 表示模型描述信息
    aclmdlDataset *inputDataset_;  // 描述模型推理时的输入数据、输出数据
    aclmdlDataset *outputDataset_;
    aclrtRunMode runMode_; // 0:运行在Device的Control CPU或板端环境上。
    void* inputBuffer_;
    void *outputBuffer_;
    size_t inputBufferSize_;
    float* imageBytes;
    String imagePath;
    Mat srcImage;
};

ResnetCls::ResnetCls(int32_t device, const char* modelPath,
                                               int32_t modelWidth, int32_t modelHeight) :
        deviceId_(device), context_(nullptr), stream_(nullptr), modelId_(0),
        modelPath_(modelPath), modelWidth_(modelWidth), modelHeight_(modelHeight),
        modelDesc_(nullptr), inputDataset_(nullptr), outputDataset_(nullptr)
{
}

ResnetCls::~ResnetCls()
{
    ReleaseResource();
}
  • 初始化资源,需要依次申请如下运行管理资源:Device、Context、Stream,使用AscendCL接口开发应用时,必须先调用aclInit接口,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。函数原型为:

aclError aclInit(const char *configPath)  // 可将配置文件配置为空json串(即配置文件中只有{})

// 依次申请如下运行管理资源:Device、Context、Stream
const char *aclConfigPath = ""; // 可将配置文件配置为空json串
aclError ret = aclInit(aclConfigPath);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclInit failed, errorCode is %d", ret);
    return FAILED;
}

ret = aclrtSetDevice(deviceId_); // 指定当前线程中用于运算的Device
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclrtSetDevice failed, errorCode is %d", ret);
    return FAILED;
}

ret = aclrtCreateContext(&context_, deviceId_); // 当前进程或线程中显式创建一个Context
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclrtCreateContext failed, errorCode is %d", ret);
    return FAILED;
}

ret = aclrtCreateStream(&stream_); // 创建一个Stream
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclrtCreateStream failed, errorCode is %d", ret);
    return FAILED;
}

加载模型并生成模型信息,即加载前面转好的resnet50.om,因为我在转换的时候没用到Ascend中的DVPP和AIPP模块(图片处理模块,如果在转换的时候加入config文件后续可以调用DVPP、AIPP进行输入图片预处理,不过没有的话用opencv处理也是一样的)。

// load model from file
ret = aclmdlLoadFromFile(modelPath_, &modelId_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclmdlLoadFromFile failed, errorCode is %d", ret);
    return FAILED;
}

// create description of model
modelDesc_ = aclmdlCreateDesc();
ret = aclmdlGetDesc(modelDesc_, modelId_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclmdlGetDesc failed, errorCode is %d", ret);
    return FAILED;
}
ret = aclrtGetRunMode(&runMode_);
if (ret == FAILED) {
    ERROR_LOG("get runMode failed, errorCode is %d", ret);
    return FAILED;
}

描述输入输出,每个输入/输出的内存地址、内存大小用aclDataBuffer类型的数据来描述。

// create data set of input
inputDataset_ = aclmdlCreateDataset();
size_t inputIndex = 0;
inputBufferSize_ = aclmdlGetInputSizeByIndex(modelDesc_, inputIndex);
aclrtMalloc(&inputBuffer_, inputBufferSize_, ACL_MEM_MALLOC_HUGE_FIRST);
aclDataBuffer *inputData = aclCreateDataBuffer(inputBuffer_, inputBufferSize_);
ret = aclmdlAddDatasetBuffer(inputDataset_, inputData);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclmdlAddDatasetBuffer failed, errorCode is %d", ret);
    return FAILED;
}

// create data set of output
outputDataset_ = aclmdlCreateDataset();
size_t outputIndex = 0;
size_t modelOutputSize = aclmdlGetOutputSizeByIndex(modelDesc_, outputIndex);
aclrtMalloc(&outputBuffer_, modelOutputSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclDataBuffer *outputData = aclCreateDataBuffer(outputBuffer_, modelOutputSize);
ret = aclmdlAddDatasetBuffer(outputDataset_, outputData);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclmdlAddDatasetBuffer failed, errorCode is %d", ret);
    return FAILED;
}
  • 处理输入图片,对输入图片进行标准化和resize等操作,计算处理后的图片byte并分配内存。

namespace {
    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;
}

Result ResnetCls::ProcessInput(const string testImgPath)
{
    // read image from file by cv
    imagePath = testImgPath;
    srcImage = imread(testImgPath);
    Mat resizedImage;

    // zoom image to modelWidth_ * modelHeight_
    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
    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));


    // 做BGR->RGB通道转换
    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] );
            }
        }
    }
    return SUCCESS;
}
  • 将输入数据memcpy到npu,并进行模型推理

aclError ret = aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("memcpy  failed, errorCode is %d", ret);
    return FAILED;
}
// inference
ret = aclmdlExecute(modelId_, inputDataset_, outputDataset_); // 执行模型推理,直到返回推理结果
if (ret != ACL_SUCCESS) {
    ERROR_LOG("execute model failed, errorCode is %d", ret);
    return FAILED;
}
  • 后处理,根据output(各个类的概率)找到概率最大的类,通过label.h对应找到分类结果。

  • 释放资源,与前面申请Device、Context、Stream的顺序相反,依次释放inputBuffer_、outputBuffer_、modelDesc_、Stream、Context、Device。

void ResnetCls::ReleaseResource()
{
    aclError ret;
    // release resource includes acl resource, data set and unload model
    aclrtFree(inputBuffer_);
    inputBuffer_ = nullptr;
    (void)aclmdlDestroyDataset(inputDataset_);
    inputDataset_ = nullptr;

    aclrtFree(outputBuffer_);
    outputBuffer_ = nullptr;
    (void)aclmdlDestroyDataset(outputDataset_);
    outputDataset_ = nullptr;

    ret = aclmdlDestroyDesc(modelDesc_);
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("destroy description failed, errorCode is %d", ret);
    }

    ret = aclmdlUnload(modelId_);
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("unload model failed, errorCode is %d", ret);
    }

    if (stream_ != nullptr) {
        ret = aclrtDestroyStream(stream_);
        if (ret != ACL_SUCCESS) {
            ERROR_LOG("aclrtDestroyStream failed, errorCode is %d", ret);
        }
        stream_ = nullptr;
    }

    if (context_ != nullptr) {
        ret = aclrtDestroyContext(context_);
        if (ret != ACL_SUCCESS) {
            ERROR_LOG("aclrtDestroyContext failed, errorCode is %d", ret);
        }
        context_ = nullptr;
    }

    ret = aclrtResetDevice(deviceId_);
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("aclrtResetDevice failed, errorCode is %d", ret);
    }

    ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("aclFinalize failed, errorCode is %d", ret);
    }
}

3、调用ResnetCls进行全pipeline的推理得到推理结果

int main()
{
    const char* modelPath = "../model/resnet50_new.om";
    const string imagePath = "../data";
    int32_t device = 0;
    int32_t modelWidth = 224;
    int32_t modelHeight = 224;

    // all images in dir
    DIR *dir = opendir(imagePath.c_str());
    if (dir == nullptr)
    {
        ERROR_LOG("file folder does no exist, please create folder %s", imagePath.c_str());
        return FAILED;
    }
    vector<string> allPath;
    struct dirent *entry;
    while ((entry = readdir(dir)) != nullptr)
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0
            || strcmp(entry->d_name, ".keep") == 0)
        {
            continue;
        }else{
            string name = entry->d_name;
            string imgDir = imagePath + "/" + name;
            allPath.push_back(imgDir);
        }
    }
    closedir(dir);

    if (allPath.size() == 0){
        ERROR_LOG("the directory is empty, please download image to %s", imagePath.c_str());
        return FAILED;
    }

    string fileName;
    ResnetCls Resnet(device, modelPath, modelWidth, modelHeight);
    Result ret = Resnet.InitResource();
    if (ret != SUCCESS) {
        ERROR_LOG("InitResource  failed");
        return FAILED;
    }

    for (size_t i = 0; i < allPath.size(); i++)
    {
        fileName = allPath.at(i).c_str();
        ret = Resnet.ProcessInput(fileName);
        if (ret != SUCCESS) {
            ERROR_LOG("ProcessInput  failed");
            return FAILED;
        }

        ret = Resnet.Inference();
        if (ret != SUCCESS) {
            ERROR_LOG("Inference  failed");
            return FAILED;
        }

        ret = Resnet.GetResult();
        if (ret != SUCCESS) {
            ERROR_LOG("GetResult  failed");
            return FAILED;
        }
    }
    return SUCCESS;
}

3.3 开发总结

整个开发流程可以总结为以下八个函数:因为用到了NPU资源,总体跟之前如ncnn后端推理的区别主要在于需要向NPU申请资源和分配内存,简单来说就是把输入copy到NPU上进行推理后再返回CPU做后处理操作。

  1. 定义一个资源初始化的函数,用于AscendCL初始化、运行管理资源申请(指定计算设备)

  2. 定义一个模型加载的函数,加载模型,用于后续推理使用

  3. 定义一个读图片数据的函数,将测试图片数据读入内存,并传输到Device侧,用于后续推理使用

  4. 定义一个推理的函数,用于执行推理

  5. 定义一个推理结果数据处理的后处理函数

  6. 定义一个模型卸载的函数,卸载已加载的模型

  7. 定义一个函数,用于释放内存、销毁推理相关的数据类型,防止内存泄露

  8. 定义一个资源去初始化的函数,用于AscendCL去初始化、运行管理资源释放(释放计算设备)

Ascend官网的流程图也很清晰:

第四章 基于OrangePi AIpro的刷掌识别系统

2023年5月,据微信公开课公众号称,微信刷掌支付正式发布,用户可以在刷脸设备上进行刷掌操作。

2023年5月22日消息,微信刷掌支付功能正式发布,用户可以在刷脸设备上进行刷掌操作。需要先在设备绑定个人微信账号,录入手掌纹样。消费时,将手掌对准支付设备的扫描区,确认后即可完成支付。

2023年9月5日消息,微信支付宣布,全国零售行业首发上线了微信刷掌支付,现已正式登陆广东711便利店1500+门店。

本博客初步实现“刷掌识别+Ascend”的demo案例,实际上刷掌识别是通过掌纹/掌脉/掌纹掌脉融合等特征来进行身份认证的,再进一步说就是图像分类问题,因此基于OrangePi AIpro中的Ascend AI芯片是完全可以实现这个任务的。

4.1 模型训练和模型导出转换

在Pytorch框架上搭建掌静脉识别模型训练框架,训练完成后得到预训练模型palm.onnx模型,模型输入为[1,1,224,224]的灰度图,本文主要聚焦于端侧的部署,因此不对模型训练作过多的阐述

得到预训练模型palm.onnx文件,进入模型文件所在位置。通过onnx可视化可以看到模型的输入为1x1x224x224,且输入input_name即为input

执行如下命令生成离线模型:

atc --model=palm.onnx --framework=5 --output=palm --input_shape="input:1,1,224,224" --soc_version=Ascend310B4 

4.2 部署框架设计

整体的代码框架如下:

palm_recognition
├── cmake-build-debug // 本地编译文件夹
├── cmake-build-debug-ascend // 远程编译文件夹
    └── palm_recognition  // 可执行文件
└── data   // 多个不同人的掌静脉图片
    └── 0.bmp
    └── 1.bmp
    └── 3.bmp
└── model  // .om模型文件
    └── palm.om
└── src  // 认证代码,PalmAuthen类的实现
    └── recognition.h
    └── recognition.cpp
└── CMakeLists.txt  // 链接库等
└── main.cpp  // 程序入口

主要参照官方sample(sampleResnetQuickStart)的推理pipeline来进行整个框架的设计,对代码进一步进行封装,主要修改适配掌静脉识别任务:

  • 针对灰度图进行标准化,根据模型训练的mean和norm对单通道图片进行标准化。

// data standardization
const float mean_value[1] = {0.5f * 255.f};
const float norm_value[1] = {1.f / 0.1f / 255.f};
// 标准化并拉平
for (int h = 0; h < resizeHeight; ++h)
{
    for (int w = 0; w < resizeWeight; ++w)
    {
        int dstIdx = h * resizeWeight + w;
        imageBytes[dstIdx] =  static_cast<float>((resizedImage.at<cv::Vec3b>(h, w)[0] -
                                                  1.0f*mean_value[0]) * 1.0f*norm_value[0]);
    }
}
  • 修改后处理函数,官方sample是对输出的1*1000向量排序并进行softmax得到最后每个类别的概率值,如下

// 放入map并按照最大到小排序
map<float, unsigned int, greater<float> > resultMap;
for (unsigned int j = 0; j < len / sizeof(float); ++j) {
    resultMap[*outData] = j;
    outData++;
}

// 做softmax将概率归一化到[0,1]
double totalValue=0.0;
for (auto it = resultMap.begin(); it != resultMap.end(); ++it) {
    totalValue += exp(it->first);
}

// 拿到概率最大的类别
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());

而我只需要拿到原始的输出1*512向量并做归一化。

// 放入result
result.clear();
for (unsigned int j = 0; j < len / sizeof(float); ++j) {
    result.push_back(*outData);
    outData++;
}
// 做归一化
normalize_feature(result, result, len / sizeof(float));
  • 在main.cpp中对两张图片进行余弦相似度计算

string fileName = "../data/0_0.bmp";
auto start = chrono::high_resolution_clock::now();
net.ProcessInput(fileName);
net.Inference();
net.GetResult();
auto end = chrono::high_resolution_clock::now();
chrono::duration<double> inference_time_use = end - start;
INFO_LOG("Feature extract time: %f ms", inference_time_use.count()* 1000);
vector<float> result1 = net.result;

fileName = "../data/40_1.bmp";
start = chrono::high_resolution_clock::now();
net.ProcessInput(fileName);
net.Inference();
net.GetResult();
end = chrono::high_resolution_clock::now();
inference_time_use = end - start;
INFO_LOG("Feature extract time: %f ms", inference_time_use.count()* 1000);
vector<float> result2 = net.result;

// 输出余弦相似度距离[0,1],1为最相似
float sim=inner_product(result1, result2, 512);
INFO_LOG("similarity: %f", sim);

以下为同个人的手掌的相似度,两个不同人的手掌的相似度,根据设置sim的阈值就可以对每个人进行身份认证。

推理pipeline遵循以下逻辑。

第五章 OrangePi AIpro使用总结和评价

5.1 总结

本文主要做了以下几点工作:

  1. 拿到OrangePi AIpro之前在虚拟机Ubuntu 2022.04上搭建CANN开发环境,为后续板端推理配合做模型转换工作,得到适配昇腾AI处理器(Ascend310B4)的离线模型xx.om。

  2. 开箱并测试 OrangePi AIpro开发板的功能,同时体验了0-1从烧录到运行系统跑通样例代码的过程并做记录。

  3. 基于samples/inference/modelInference/sampleResnetQuickStart 在CLion上搭建工程并远程编译运行图像分类功能。

  4. 基于3的工作将图像分类应用到刷掌识别领域,设计轻量化模型并成功转换模型部署到了OrangePi AIpro上,基本实现了最核心的识别功能,推理性能相比之前CPU推理的后端框架快了将近10倍

5.2 使用体验 

  1. OrangePi AIpro搭载了昇腾Ascend310B4处理器,可提供8TOPS INT8 的计算能力,将AI推理任务Memcpy到NPU上推理后再返回到Host,大大加快了推理速度。性能的强大使得跟多有意思的AI应用可以部署,如视频理解这种任务。

  2. Ascend的官方提供的资料,包括ATC工具使用、ACL开发、ACL接口文档等等都非常齐全,并且在开源仓库提供了很多示例代码,非常有助于初学者上手并开发自己的第一个AI应用,让开发者可以便捷高效的编写在特定硬件设备上运行的AI应用程序。

  3. OrangePi AIpro板子本身提供了非常丰富的外设和接口,可以在此基础上结合项目需求做到软硬件配合

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CANOE制造dll文件,以及应用dll文件

1、使用canoe自带的capl dll 2、然后使用Visual Studio 2022 打开项目 3、项目打开后修改下项目属性 4、修改capldll.cpp文件 4.1 添加的内容 void CAPLEXPORT far CAPLPASCAL appSum(long i, long j, long* s){*s i j;} {"sum", (CAPL_FARCALL)appSum, "…

FinalShell无法连接Linux

Linux使用Vmware会创建一个网络&#xff0c;让两个子网处于一个网关&#xff0c;这样就能在windows中连接Linux&#xff0c;只有在这种情况下才能FinalShell才能连接Linux

Redis 和 Mysql 如何保证两者数据一致性

文章目录 概述解决方案消息队列异步重试 基于 RocketMQ 的可靠性消息通信&#xff0c;来实现最终一致Canal 组件&#xff0c;监控 Mysql 中 binlog 的日志&#xff0c;把更新后的数据同步到 Redis 里面延时双删弱一致性和强一致性Canal详解 概述 在分布式系统中&#xff0c;保…

2024中国军民两用智能装备与通信技术产业展览会带你走进轻元素量子材料世界

在科技创新的浪潮中&#xff0c;北京怀柔科学城迎来了一场革命性的突破——世界首个轻元素量子材料平台正式启动运行。这一里程碑事件不仅彰显了中国在量子科学研究上的领先地位&#xff0c;也为全球科技界带来了一股新风潮。由北京大学领衔打造的这一平台&#xff0c;专注于轻…

[论文笔记]MemGPT: Towards LLMs as Operating Systems

引言 今天介绍一篇论文MemGPT: Towards LLMs as Operating Systems。翻过过来就是把LLM看成操作系统。 大语言模型已经在人工智能领域引起了革命性的变革&#xff0c;但受到有限上下文窗口的限制&#xff0c;在扩展对话和文档分析等任务中的效用受到了阻碍。为了能够利用超出…

免费生物蛋白质的类chatgpt工具助手copilot:小分子、蛋白的折叠、对接

参考: https://310.ai/copilot 可以通过自然语言通话晚上蛋白质的相关处理:生成序列、折叠等 应该是agent技术调用不同工具实现 从UniProt数据库中搜索和加载蛋白质。使用ESM Fold方法折叠蛋白质。使用310.ai基础模型设计新蛋白质。使用TM-Align方法比较蛋白质。利用Protei…

谁是镰刀谁是韭菜?程序交易与手动交易的博弈,靠技术还是靠运气

备受争议的话题&#xff0c;很多人认为程序化交易是在破坏市场的平衡&#xff0c;大量的程序交易订单可能会造成市场价格的异常波动&#xff0c;尤其是在高频交易未被监管时&#xff0c;程序化交易者占尽优势&#xff0c;来回收割。 而支持程序交易的人认为&#xff0c;市场是…

Java八股文:程序员的“面试经”还是技术壁垒?

Java八股文&#xff1a;程序员的“面试经”还是技术壁垒&#xff1f; “八股文”&#xff0c;在中国古代科举考试中&#xff0c;指的是一种程式化的文章写作格式&#xff0c;内容空洞&#xff0c;缺乏创新。而如今&#xff0c;这个词语被赋予了新的含义&#xff0c;用来形容技术…

python基础(习题、资料)

免费提取资料&#xff1a; 练习、资料免费提取。持续更新迅雷云盘https://pan.xunlei.com/s/VNz6kH1EXQtK8j-wwwz_c0k8A1?pwdrj2x# 本文为Python的进阶知识合辑&#xff0c;包括列表&#xff08;List&#xff09;、元组&#xff08;Tuple&#xff09;、字典&#xff08;Dic…

微信密码忘记了怎么找回?自助找回2个方法揭晓!

在微信的世界里&#xff0c;密码就像是我们通往个人世界的“钥匙”&#xff0c;一旦丢失&#xff0c;就仿佛被锁在了自己的门外。微信密码忘记了怎么找回&#xff1f;别担心&#xff0c;微信提供了多种自助找回密码的方法&#xff0c;让我们一起来揭秘这些找回密码的秘诀吧&…

在全志H616核桃派开发板上配置SSH远程终端方法详解

熟悉指令用户可以对已经联网的核桃派进行局域网SSH远程终端控制&#xff0c;方便使用自己的PC对核桃派远程进行各种指令操作。 普通用户&#xff08;默认&#xff09; 账号&#xff1a;pi ; 密码&#xff1a;pi管理员账户 账号&#xff1a;root ; 密码&#xff1a;root 在这之…

【MySQL访问】

文章目录 一、C远程连接到MySQLmysql_init()函数mysql_real_connect&#xff08;&#xff09;函数实战案例 二、处理查询select的细节mysql_store_result()函数获取结果行和列获取select结果获取行内容获取列属性 三、MySQL图形化界面连接 关于动态链接&#xff0c;请看这篇文章…

【C语言】基于C语言实现的贪吃蛇游戏

【C语言】基于C语言实现的贪吃蛇游戏 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【C语言】基于C语言实现的贪吃蛇游戏前言一.最终实现效果一.Win32 API介绍1.1Win32 API1.2控制台程序1.3控制台屏幕上的坐标COORD…

类和对象(中)【类的6个默认成员函数】 【零散知识点】 (万字)

类和对象&#xff08;中&#xff09; 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1…

TiDB-从0到1-体系结构

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCC 一、TiDB体系结构图 TiDB基础的体系架构中有4大组件 TiDB Server&#xff1a;用于处理客户端的请求PD&#xff1a;体系的大脑&#xff0c;存储元数据信息TiKV&#xff1a…

kvm--安装启动

前期 使用vmware workstation 时 安装kvm yum install qemu-kvm qemu-kvm-tools libvirt libvirt-client virt-manager virt-install -y systemctl enable --now libvirtd

【数据结构】二叉树运用及相关例题

文章目录 前言查第K层的节点个数判断该二叉树是否为完全二叉树例题一 - Leetcode - 226反转二叉树例题一 - Leetcode - 110平衡二叉树 前言 在笔者的前几篇篇博客中介绍了二叉树的基本概念及基本实现方法&#xff0c;有兴趣的朋友自己移步看看。 这篇文章主要介绍一下二叉树的…

使用cad绘制一个螺旋输送机

1、第一步&#xff0c;绘制一个矩形 2、使用绘图中的样条线拟合曲线&#xff0c;绘制螺旋线。 绘制时使用上下辅助线、阵列工具绘制多个竖线保证样条线顶点在同一高度。 3、调整矩形右侧的两个顶点&#xff0c;使其变形。 矩形1和矩形2连接时&#xff0c;使用blend命令&#…

Nginx(openresty) 开启目录浏览 以及进行美化配置

1 nginx 安装 可以参考:Nginx(openresty) 通过lua结合Web前端 实现图片&#xff0c;文件&#xff0c;视频等静态资源 访问权限验证&#xff0c;进行鉴权 &#xff0c;提高安全性-CSDN博客 2 开启目录浏览 location /file{alias /data/www/; #指定目录所在路径autoindex on; …