OrangePi AIpro学习3 —— vscode开发昇腾DVPP程序

目录

一、VScode配置

1.1 下载和安装

1.2 安装和配置需要的插件

二、构建项目

2.1 项目架构

2.2 解决代码高亮显示

2.3 测试编译

2.4 总结出最简单的代码

2.5 vscode报错找不到头文件解决方法

三、代码简单讲解

3.1 初始化部分

3.2 拷贝数据到NPU显存中

3.3 准备裁剪区域

3.4 准备输入输出描述符

3.5 收尾阶段

3.6 其他例程如何学习

四、DVPP输入输出图片限制

4.1 裁剪的输入图片要求

4.2 裁剪的输出图片要求

4.3 输出图片的格式和宽高

4.4 对输出的图片进行处理


一、VScode配置

1.1 下载和安装

1. 下载地址

Download Visual Studio Code - Mac, Linux, Windows

2. 安装中要注意的内容

1.2 安装和配置需要的插件

1. chinese中文

如果在这里安装不了的话,可以使用离线安装的方式,首先进入官网下载离线插件包:Extensions for Visual Studio family of products | Visual Studio Marketplace

然后在vscode中这么操作,选择刚刚下载的VSIX文件即可完成安装

2. 安装远程工具Remote

设置要连接的主机,然后两次回车

重启vscode

选择linux然后再输入密码

进入文件夹

二、构建项目

2.1 项目架构

data数据是从上面的crop中拿过来的 

cmake_minimum_required(VERSION 3.22)

# 设置变量PROJECT_NAME的值为crop
set(PROJECT_NAME "crop")

# 设置项目名称为crop
project(${PROJECT_NAME})

# 添加dvpp需要的宏定义,相当于在代码中: #define ENABLE_DVPP_INTERFACE
add_definitions(-DENABLE_DVPP_INTERFACE)

# 设置头文件目录
include_directories(
    $ENV{INSTALL_DIR}/runtime/include/
    ./include
)

# 设置动态库目录
link_directories(
    $ENV{INSTALL_DIR}/runtime/lib64/stub
)

# 查找所有CMakeLists.txt所在目录下,src目录中的.c和.cpp文件
file(GLOB_RECURSE CPP_FILES     
    ${PROJECT_SOURCE_DIR}/src/*.c
    ${PROJECT_SOURCE_DIR}/src/*.cpp
)

# 编译输出的文件名为: crop  依赖src目录下的所有.c和.cpp结尾的文件
add_executable(${PROJECT_NAME}
    ${CPP_FILES}
)

target_link_libraries(${PROJECT_NAME}
    ascendcl 
    acl_dvpp
    stdc++
)

2.2 解决代码高亮显示

CMake或者cpp没有高亮显示就上传vsix文件到香橙派

然后在vscode中安装 

2.3 测试编译

提示要增加一行内容,这行内容指定了CMake需要的最小版本

 然后./crop就能运行程程序了

2.4 总结出最简单的代码

#include <iostream>
#include <memory>
#include <dirent.h>
#include <fstream>
#include "acl/acl.h"
#include "acl/ops/acl_dvpp.h"

using namespace std;

typedef struct PicDesc {
    string picName;
    int left;
    int top;
    int width;
    int height;
} PicDesc;


aclrtContext g_context;
aclrtStream g_stream;
aclrtRunMode g_runMode;
acldvppChannelDesc * g_dvppChannelDesc;


char * ReadBinFile(string fileName, uint32_t &fileSize)
{
    ifstream binFile(fileName, ifstream::binary);
    if (binFile.is_open() == false) 
    {
        printf("Open file %s failed.\n", fileName.c_str());
        return nullptr;
    }

    binFile.seekg(0, binFile.end);
    uint32_t binFileBufferLen = binFile.tellg();
    if (binFileBufferLen == 0) 
    {
        printf("Binfile is empty, filename is %s.\n", fileName.c_str());
        binFile.close();
        return nullptr;
    }

    binFile.seekg(0, binFile.beg);

    char * binFileBufferData = new(nothrow) char[binFileBufferLen];
    if (binFileBufferData == nullptr) {
        printf("Malloc binFileBufferData failed.\n");
        binFile.close();
        return nullptr;
    }
    binFile.read(binFileBufferData, binFileBufferLen);
    binFile.close();
    fileSize = binFileBufferLen;
    return binFileBufferData;
}

uint32_t AlignmentHelper(uint32_t origSize, uint32_t alignment)
{
    if (alignment == 0) {
        return 0;
    }
    uint32_t alignmentH = alignment - 1;
    return (origSize + alignmentH) / alignment * alignment;
}

uint32_t SaveDvppOutputData(const char *fileName, const void *devPtr, uint32_t dataSize)
{
    FILE * outFileFp = fopen(fileName, "wb+");
    if (g_runMode == ACL_HOST) {
        void * hostPtr = nullptr;
        aclrtMallocHost(&hostPtr, dataSize);
        aclrtMemcpy(hostPtr, dataSize, devPtr, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
        fwrite(hostPtr, sizeof(char), dataSize, outFileFp);
        (void)aclrtFreeHost(hostPtr);
    } else {
        fwrite(devPtr, sizeof(char), dataSize, outFileFp);
    }
    fflush(outFileFp);
    fclose(outFileFp);
    return 0;
}

int main()
{
    /* 
     * 初始化device、context、stream和dvppChannelDesc,并获取运行模式
     */
    aclInit("./src/acl.json");
    aclrtSetDevice(0);
    aclrtCreateContext(&g_context, 0);
    aclrtCreateStream(&g_stream);
    g_dvppChannelDesc = acldvppCreateChannelDesc(); // 创建图像数据处理通道时的通道描述信息 g_dvppChannelDesc is acldvppChannelDesc type
    acldvppCreateChannel(g_dvppChannelDesc);        // 创建图像数据处理通道
    aclrtGetRunMode(&g_runMode);


    /* 
     * 读取输入和输出
     */
    // 下面设置输入图片的信息,图片的left和top参数用不到
    PicDesc inPicDesc  = { "./data/wood_rabbit_1024_1068_nv12.yuv", 0, 0, 1024, 1068 };
    // 下面设置输出图片的信息
    PicDesc outPicDesc = { "./output/dvpp_rabbit_224_224_nv12.yuv", 350, 280, 224, 224 };
    

    /* 
     * 根据输入读取文件到NPU显存中
     */
    uint32_t buffSize = 0;
    // inPicDesc.picName:要读取的文件名     buffSize:得到的文件大小    inputBuff:得到的文件的内容
    char * inputBuff = ReadBinFile(inPicDesc.picName, buffSize);

    void * inputBufferDev = nullptr;
    acldvppMalloc(&inputBufferDev, buffSize);     // 在NPU上分配显存
    if (g_runMode == ACL_HOST)  // 如果本段C++代码是运行在CPU上
    {                           // 把数据从内存条拷贝到NPU的显存中
        aclrtMemcpy(inputBufferDev, buffSize, inputBuff, buffSize, ACL_MEMCPY_HOST_TO_DEVICE);
    } 
    else                        // 如果本段C++代码是运行在NPU中的CPU上
    {                           // 把数据从NPU显存拷贝到NPU显存中
        aclrtMemcpy(inputBufferDev, buffSize, inputBuff, buffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
    }
    delete[] inputBuff;


    /*
     * 设置要裁剪输入图片的区域
     */
    uint32_t outputWidth = AlignmentHelper(outPicDesc.width, 16);   // 输入图片宽度按照16字节对其,例如输入17,得到的outputWidth就是32
    uint32_t outputHeight = AlignmentHelper(outPicDesc.height, 2);  // 输入图片高度按照2字节对其,例如输入13,得到的outputHeight就是2
    uint32_t cropLeftOffset = outPicDesc.left;                      // must 偶数
    uint32_t cropRightOffset = cropLeftOffset + outputWidth - 1;    // must 奇数
    uint32_t cropTopOffset = outPicDesc.top;                        // must 偶数
    uint32_t cropBottomOffset = cropTopOffset + outputHeight - 1;   // must 奇数
    acldvppRoiConfig * cropArea_ = acldvppCreateRoiConfig(cropLeftOffset, cropRightOffset, cropTopOffset, cropBottomOffset);


    /*
     * 创建输入图片描述符,并填写输入图片信息
     */
    uint32_t inputWidthStride = AlignmentHelper(inPicDesc.width, 16);
    uint32_t inputHeightStride = AlignmentHelper(inPicDesc.height, 2);
    uint32_t inputBufferSize = inputWidthStride * inputHeightStride * 3 / 2;

    acldvppPicDesc * vpcInputDesc_ = acldvppCreatePicDesc();            // 创建入参数描述符
    acldvppSetPicDescData(vpcInputDesc_, inputBufferDev);               // 原图片存放位置的指针
    acldvppSetPicDescFormat(vpcInputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420);    // 原图片的格式
    acldvppSetPicDescWidth(vpcInputDesc_, inPicDesc.width);             // 原图片宽度
    acldvppSetPicDescHeight(vpcInputDesc_, inPicDesc.height);           // 原图片高度
    acldvppSetPicDescWidthStride(vpcInputDesc_, inputWidthStride);      // 原图片对齐后的宽度
    acldvppSetPicDescHeightStride(vpcInputDesc_, inputHeightStride);    // 原图片对齐后的高度
    acldvppSetPicDescSize(vpcInputDesc_, inputBufferSize);              // 输入图片的图片大小


    /*
     * 创建输出图片描述符,并填写输出图片信息
     */
    void * outputBufferDev = nullptr;
    uint32_t outputBufferSize = outputWidth * outputHeight * 3 / 2;
    acldvppMalloc(&outputBufferDev, outputBufferSize);                  // 在NPU上给输出图片分配内存

    acldvppPicDesc * vpcOutputDesc_ = acldvppCreatePicDesc();
    acldvppSetPicDescData(vpcOutputDesc_, outputBufferDev);             // 输出图片存放位置指针
    acldvppSetPicDescFormat(vpcOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420);   // 输出图片的格式
    cout << outPicDesc.width << " " << outPicDesc.height << " " << outputWidth << " " << outputHeight << endl;
    acldvppSetPicDescWidth(vpcOutputDesc_, outPicDesc.width);           // 输出图片的宽
    acldvppSetPicDescHeight(vpcOutputDesc_, outPicDesc.height);         // 输出图片的高
    acldvppSetPicDescWidthStride(vpcOutputDesc_, outputWidth);          // 输出图片对齐后的宽
    acldvppSetPicDescHeightStride(vpcOutputDesc_, outputHeight);        // 输出图片对齐后的高
    acldvppSetPicDescSize(vpcOutputDesc_, outputBufferSize);

    /*
     * 图片的处理和保存
     */
    // 由g_stream这个流水线来运行处理程序
    acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);
    // 等待g_stream流水线完成
    aclrtSynchronizeStream(g_stream);

    // 保存图片
    SaveDvppOutputData(outPicDesc.picName.c_str(), outputBufferDev, outputBufferSize);
    

    /*
     * 释放占用的资源
     */
    acldvppFree(outputBufferDev);
    acldvppDestroyRoiConfig(cropArea_);
    acldvppDestroyPicDesc(vpcInputDesc_);
    acldvppDestroyPicDesc(vpcOutputDesc_);
    aclrtDestroyStream(g_stream);
    aclrtDestroyContext(g_context);
    aclrtResetDevice(0);
    aclFinalize();

    return 0;
}

下图代表裁剪成功了

2.5 vscode报错找不到头文件解决方法

按 F1 或 Ctrl+Shift+p 在弹出的备选选项中选择 C/C++:Edit Configurations (JSON),自动打开c_cpp_properties.json配置文件

 在.vscode中添加头文件搜索路径的json,然后配置路径

三、代码简单讲解

下面的内容全是我个人的理解,如果有佬,感谢在评论区指正,我会及时修改文章

3.1 初始化部分

88行:aclInit("./src/acl.json");

● 一个进程内只能调用一次aclInit接口。使用AscendCL接口开发应用时,必须先调用aclInit接口,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。

● 输入参数是一个json格式的文件。json里面可以写啥,这个可以看官方文档:

aclInit-系统配置-AscendCL API(C&C++)-应用开发接口-CANN社区版8.0.RC2.alpha001开发文档-昇腾社区 (hiascend.com)

目前我是在json里面就写了一对花括号:{}

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

89行:aclrtSetDevice(0);

● 我的理解就是类似于下图(下图是我的猜想,并不来自于官方),一个板子上面有3块昇腾310B的芯片。上面的代码就是设置本线程(一个进程有一个主线程)使用索引为0的昇腾芯片

● 查看官方的device操作发现,aclrtGetDeviceCount函数可以获取Device的数量,我觉得如果有3个昇腾芯片,可能返回的是3。目前香橙派上面只有一个昇腾310B芯片,返回的就是1

hiascend.com/doc_center/source/zh/canncommercial/601/inferapplicationdev/aclcppdevg/aclcppdevg_03_0019.html

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

90行:aclrtCreateContext(&g_context, 0);

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

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

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

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

91行:aclrtCreateStream(&g_stream);

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

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

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

92、93行:g_dvppChannelDesc = acldvppCreateChannelDesc();
acldvppCreateChannel(g_dvppChannelDesc);

● 这个是图片处理函数必须要的一个参数,在这里提前准备一下
acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);

94行:aclrtGetRunMode(&g_runMode);

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

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

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

3.2 拷贝数据到NPU显存中

● 111行,通过ReadBinFile函数读取数据到内存中。如果程序是运行在CPU上,那么读取到的数据就放在运行内存上;如果程序是运行在昇腾芯片的AI CPU上,那么读取到的数据就放在昇腾芯片的内存,也就是显存上

● 114行,在显存上分配一段文件大小的内存。

● 115行,做判断。如果程序是运行在CPU上,就要把数据从运行内存上搬运到显存上;如果程序是运行在昇腾芯片的AI CPU上,那么就把数据从显存搬运到显存上(这里也可以不搬运,主要是为了后面写库的时候使用)

在运行内存上对应的就是HOST,到显存上对应的就是TO_DEVICE;在显存上对应的就是DEVICE,到显存上对应的就是TO_DEVICE。同理也可以HOST_TO_HOST,DEVICE_TO_HOST。

补充一下:如果程序运行在昇腾AI CPU上,想要在运行内存中分配内存,就需要使用aclrtMallocHost来申请内存

3.3 准备裁剪区域

上面分别生成了cropArea_,用于告诉下面的函数,输入图片要裁剪的范围

acldvppVpcCropAsync(g_dvppChannelDesc, vpcInputDesc_, vpcOutputDesc_, cropArea_, g_stream);

这里为什么减1呢,我猜测可能是因为DVPP主要应用于人工智能,少一行信息对人工智能来说是没影响的

3.3和3.4为什么要使用AlignmentHelper对输入和输出数据进行16位和2位对齐呢,这要看第四节讲的内容

3.4 准备输入输出描述符

● 143行,为什么输入的图片大小是这么计算的呢

首先是对齐后图片的宽高如下图,前面这部分很好理解:

● 后面的*3,是因为存储图片的是YUV 3个通道,这样就需要 对齐后的宽高*3得到需要的字节数,

● 那么为什么要除2呢?这是因为YUV本来YUV三个通道都应该有4个信息,也就是8bit来表示Y,8bit来表示U,8bit来表示V。YUV420图片,有8bit来表示Y,只有4bit来表述U,0bit来表示V,字节数自然就少了一半,所以要/2

3.5 收尾阶段

在图片处理的过程中,程序可以异步的去执行了,但是176行,我们任然阻塞的去等待图片处理结果。等到图片处理结束,就将输出的图片指针和大小传入181行的函数中,函数会将图片保存到picName指定的位置去

做完上面这些任务,就释放掉占用的资源

3.6 其他例程如何学习

把其他历程中的关键内容抽取出来,写成像上面那种形式的程序,多动手练习。自己依次分析函数的功能等。

上面代码过程分为这几步:初始化->读取要处理的图片->创建输入图片描述符->创建输出图片描述符->acl处理图片->把输出的图片保存->释放占用的资源

四、DVPP输入输出图片限制

4.1 裁剪的输入图片要求

官方文档:

hiascend.com/doc_center/source/zh/canncommercial/601/inferapplicationdev/aclcppdevg/aclcppdevg_03_0168.html#ZH-CN_TOPIC_0000001405674684__section17242141311416

输入图片有4个信息,图片宽度按照2字节对齐,高度按照2字节对齐。还有两个信息是按照宽16字节对齐,高2字节对齐输入进去的

4.2 裁剪的输出图片要求

直观的看一下,如果我们指定输出的图片宽高不是按照2字节对齐的,输出出来的图片对应的就是绿色

输出描述符中的图片宽高要求2字节对齐:

输出图片另外两个信息分别是,宽16字节对齐,高2字节对齐

4.3 输出图片的格式和宽高

经过上面一通运算,输出出来的yuv图片宽高是按照原图片,宽=原图片按16字节对齐,高=原图片按2字节对齐

最后输出的图片右边有黑边

4.4 对输出的图片进行处理

我不太会使用AIPP的工具,所以使用python对输出的图片进行处理

读取yuv格式图片,对它进行裁剪,再输出jpg格式图片。

所以,我们现在学习这个DVPP的目的是干嘛呢?opencv不是一样可以处理,当然是有原因的,DVPP是使用硬件加速,大量的运算都交给昇腾完成,我们的CPU只需要执行好自己的复杂任务。

import numpy as np
import cv2
import sys

def AlignmentHelper(origSize, alignment):
    alignmentH = alignment - 1
    return (origSize + alignmentH) // alignment * alignment

width = int(sys.argv[1])
height = int(sys.argv[2])
yuv_file = "./output/dvpp_rabbit_" + sys.argv[1] + "_" + sys.argv[2] + "_nv12.yuv"
alignment_width = AlignmentHelper(width, 16)
alignment_height = AlignmentHelper(height, 2)
yuv_file = open(yuv_file, "rb")

frame_size = alignment_width * alignment_height * 3 // 2
data = yuv_file.read(frame_size)
yuv_image = np.frombuffer(data, dtype=np.uint8)
yuv_image = yuv_image.reshape((alignment_height*3//2, alignment_width))
bgr_image = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2BGR_I420)

patch_tree = bgr_image[0:width, 0:height]
cv2.imwrite("./output/dvpp_rabbit_" + sys.argv[1] + "_" + sys.argv[2] + "_nv12.jpg", patch_tree)
  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

herb.dr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值