2. 数据预处理
2.1 数据预处理概述
数据预处理提供两个接口AIPP和DVPP。AIPP前文已经介绍过,这部分主要介绍DVPP(数字视觉与处理)
下文**“数字视觉与处理”**专指DVPP。
DVPP提供了五个功能模块,视频编码(VENC)、视频解码(VDEC)、JPEG图像编码(JPEGE)、JPEG图像解码(JPEGD)、视觉与处理模块(VPC)。VPC模块还提供了如格式转换,图像缩放,裁剪等其他功能
- VPC:抠图、缩放、叠加、拼接、格式转换
- JPEGD:解码为YUV格式
- JPEGE:将YUV编码为.jpg
- VDEC:视频解码
- VENC:视频编码
与数据预处理相关的内存必须通过acldvppMalloc接口申请,通过acldvppFree接口释放。除了acldvppMalloc和acldvppFree,数字视觉预处理(DVPP)其他接口只能再HOST上调用,不能在Device上调用。
2.2 数字视觉预处理共用接口
- 整体调用流程:创建通道描述——创建通道——创建图片描述——执行操作——销毁图片描述——创建图片描述——执行操作——销毁图片描述——销毁通道——销毁通道描述
1. dvpp内存的申请与释放
acldvppMalloc接口负责分配内存给Device侧媒体数据预处理时使用,申请后必须用acldvppFree释放(不能delete),内存地址32对齐。频繁调用acldvppMalloc与acldvppFree对,对造成性能损耗。
aclError acldvppMalloc(void **devPtr, size_t_size)
// devPtr, 输出:Device上已分配内存的指针的指针
// size, 输入: 申请内存的大小
aclError acldvppFree(void *devPtr)
// devPtr, 输入:待释放内存的指针
若用户使用acldvppMalloc接口申请大块内存并自行划分、管理内存时,用户在管理内存时,需按每张图片的实际数据大小向上对齐成32整数倍+32字节(ALIGN_UP[len]+32字节)来管理内存。如管理n张图片内存,每张大小为X个字节,则实际应按 n × ( ALIGN_UP(X) + 32字节)大小管理内存。每张图片地址按 ALIGN_UP(X) + 32字节偏移。
- ALIGN_UP = ((len - 1)/32 + 1) × 32
2. 通道&通道描述创建与销毁
创建通道描述 -> 创建通道 -> 销毁通道 -> 销毁通道描述信息
// 通道描述创建与销毁
acldvppChannelDesc *acldvppCreateChannelDesc()
// 函数功能:创建acldvppChannelDesc类型数据,表示创建图片数据处理通道时的通道描述信息,为同步接口
// 返回acldvppChannelDesc类型表示成功;失败返回 null
aclError acldvppDestroyChannelDesc(acldvppChannelDesc *channelDesc)
// 必须调用acldvppDestroyChannel(销毁通道)后,再调用acldvppDestroyChannelDesc(销毁通道信息),否则报错
// channelDesc输入
// ”通道“创建与销毁
aclError acldvppCreateChannel(acldvppChannelDesc* channelDesc)
// 函数功能:创建通道,同一通道可以反复使用,销毁后不可再使用
// 约束:通道为非线程安全,即不同线程要求创建不同通道
// channelDesc,输入与输出:指定通道描述信息
aclError acldvppDestroyChannel(acldvppChannel *channel)
3. 创建与销毁描述图片描述信息
acldvppPicDesc* acldvppCreatePicDesc()
// 创建图片描述信息,同步接口
// 返回acldvppPicDesc类型数据表示成功,返回null则失败
aclError acldvppDestroyPicDesc(acldvppPicDesc* picDesc)
// 销毁由acldvppCreatePicDesc创建的图片描述信息
4. acldvppSetPicDesc系列接口——设置图片描述
2.3 JPEG解码
- 像素对齐
针对不同的编码格式,解码后输出不同格式的图片,如YUV444SP,YUV422SP,YUV420SP。解码对输出图片有对齐要求:宽128,高16.即,假如原始输出宽高为(129,17)应对齐为(256,32)
2.3.1 解码约束
- 输入约束:
- 最大分辨率:8192×8192, 最小分辨率:32×32
- 只支持哈夫曼编码,码流色域为YUV,下采样码流为444,422,420,400
- 不支持渐进JPEG
- 不支持JPEG2000
- 输出要求:
- 宽对齐128,高对齐16(像素对齐)
- 内存大小:内存大小与输出图片数据格式相关
- YUV420SP: widthStride × heightStride × 3 / 2
- YUV422SP: widthStride × heightStride × 2
- YUV444SP: widthStride × heightStride × 3
DVPP对输出的图片宽高有对齐要求:宽128,高16(指像素对齐而不是内存对齐)
2.3.2 解码流程图
2.3.3 图片解码接口
1. 解码接口
aclError acldvppJpegDecodeAsync(
acldvppChannelDesc *channelDesc, // 输入、通道描述,与调用acldvppCreateChannel时传入的channelDesc一致
const void *data, // 输入图片的内存地址
uint32_t size, // 输入图片的实际数据大小,单位Byte
acldvppPicDesc* outputDesc, // 输出图片信息。
aclrtStream stream // 指定此接口绑定的stream
)
// 关于outputDesc
// 1. 调用acldvppCreatePicDesc接口,创建图片描述信息
// 2. JPEG原图的高宽,可以通过acldvppJpegGetImageInfo获取
// 3. 输出图片的内存大小可提前调用acldvppJpegPredictDecSize接口获取
// 4. 输出图片格式支持设置成jpeg原图编码格式或NV12或NV21格式
2. 读取图像宽高及通道数
aclError acldvppJpegGetImageInfo(
const void* data, // 图像内存地址
uint32_t size, // 实际数据大小,单位Byte
uint32_t *width,
uint32_t *height,
int32_t *components // 通道个数
)
3. 预测输出内存
自动计算输出占用内存
aclError acldvppJpegPredictDecSize(
const void* data, // 图像内存地址
uint32_t size, // 实际数据大小,单位Byte
acldvppPixelFormat outputPixelFormat, // 解码后输出图片的格式
uint32_t *decsize // 返回JPEG图片解码后所需输出内存的大小
)
2.3.4 图像解码Demo
头文件 #include "acl/ops/acl_dvpp.h"
1. jpegd.h
# pragma once
# include "acl/acl.h"
# include <iostream>
# include <cstdint>
// DVPP头文件
# include "acl/ops/acl_dvpp.h"
# define INFO_LOG(fmt, args...) fprintf(stdout, "[INFO]" fmt "\n", ##args)
# define WARN_LOG(fmt, args...) fprintf(stdout, "[WARN] " fmt "\n", ##args)
# define ERROR_LOG(fmt, args...) fprintf(stdout, "[ERROR] " fmt "\n", ##args)
typedef enum Result {
SUCCESS = 0 ,
FAILED = 1
}Result;
typedef struct PicDesc {
std::string picName;
int width;
int height;
}PicDesc;
// 根据图片描述信息获取Device内存
static void* GetDeviceBufferOfPicture(const PicDesc &picDesc, uint32_t &devPicBufferSize);
// 从硬盘取文件
static char* ReadBinFile(std:: string fileName, uint32_t &fileSize);
// 保存dvpp输出结果至文件
static Result SaveDvppOutputData(const char *fileName,const void *devPtr, uint32_t datasize);
// 设置输入
void SetInput(void *inDevBuffer, int inDevBufferSize, int inputWidth, int inputHeight);
// 获取输出
void GetOutput(void **outputBuffer, int &outputSize);
void DestroyResource();
void DestroyDecodeResource();
int32_t deviceId_ = 0;
aclrtContext context_= nullptr;
aclrtStream stream_=nullptr;
acldvppChannelDesc *dvppChannelDesc_; // 频道描述信息
void* decodeOutDevBuffer_; // 解码输出内存地址
acldvppPicDesc *decodeOutputDesc_; // 解码输出描述信息
uint32_t decodeDataSize_; // 解码输出内存大小
void *inDevBuffer_; // 解码输入内存地址
uint32_t inDevBuffersize_; // 解码输入内存大小
uint32_t inputwidth_;
uint32_t inputHeight_;
2. jpegd.cpp
# include "acl/acl.h"
# include <iostream>
# include <jpegd.h>
# include <fstream>
# include <cstring>
# include <sys/stat.h>
# include "acl/ops/acl_dvpp.h"
using namespace std;
void CreateResource(); // 申请运行管理资源
void CreateDecodeResource(); // 创建图像数据处理通道
int main(){
// ACL初始化
const char* aclConfigPath = "../src/acl.json";
aclInit(aclConfigPath);
INFO_LOG("acl init success");
// 申请运行管理资源
CreateResource();
// 创建图像数据处理通道
CreateDecodeResource();
// 预置的图片数据
std::string dvppOutputfileName = "./dvpp_output_";
PicDesc testPic[]= {
{"../data/dog1_1024_683.jpg", 1024, 683} ,
{"../data/dog2_1024_683.jpg", 1024, 683} ,
};
// 开始循环处理图片
for (size_t index = 0; index < sizeof(testPic) / sizeof(testPic[0]);++index) {
INFO_LOG ("start to process picture:%s",testPic[index], picName.c_str());
// 将图片读入内存并复制到dvppMalloc申请的内存中
uint32_t devPicBufferSize;
void *picDevBuffer = GetDeviceBufferOfPicture(testPic[index], devPicBufferSize);
if (picDevBuffer == nullptr){
ERROR_LOG ("get pic device buffer failed , index is 8zu", index);
return FAILED ;
}
SetInput(picDevBuffer, devPicBufferSize, testPic[index].width, testPic[index].height);
// 计算解码输出图片对齐后的宽高
uint32_t decodeOutWidthStride = (inputWidth_ + 127) / 128 * 128;
uint32_t decodeOutHeightStride = (inputHeight_ + 15) / 16 * 16;
// 申请解码输出内存
aclError ret = acldvppMalloc(&decodeOutDevBuffer_, decodeDataSize_); // 按计算的内存大小申请空间
if (ret != ACL_ERROR_NONE) {
ERROR_LOG("acldvppMalloc jpegOutBufferDev failed, ret = %d", ret);
return FAILED;
}
// 创建解码输出图片描述信息
decodeOutputDesc_ = acldvppCreatePicDesc();
if (decodeOutputDesc_ != ACL_ERROR_NONE) {
ERROR_LOG("acldvppCreatePicDesc decodeOutputDesc failed");
return FAILED;
}
// 添加详细的图片描述信息
acldvppSetPicDescData(decodeOutputDesc_, decodeOutDevBuffer_); // 解码图片内存地址
acldvppSetPicDescFormat(decodeOutputDesc_,PIXEL_FORMAT_YUV_SEMIPLANAR_420); // 色域
acldvppSetPicDescWidth(decodeOutputDesc_, inputWidth_); // 输入图片宽
acldvppSetPicDescHeight(decodeOutputDesc_, inputHeight_);
acldvppSetPicDeseWidthStride(decodeOutputDesc_, decodeOutWidthStride); // 输出宽对齐
acldvppSetPicDescHeightStride(decodeOutputDesc_, decodeOutHeightStride); // 输出高对齐
acldvppSetPicDescSize(decodeOutputDese_, decodeDataSize_); // 数据大小
/* ========================= 数据准备完毕 ================================== */
// 执行解码动作
ret = acldvppJpegDecodeAsync(dvppChannelDesc_, inDevBuffer_, inDevBufferSize_, decodeOutputDesc_, stream_);
if (ret != ACL_ERROR_NONE){
ERROR_LOG("acldvppJpegDecodeAsync failed, ret=%d", ret);
return FAILED;
}
// 同步等待
// 使得程序阻塞在此,直到上一步执行解码动作完成
ret = aclrtSynchronizeStream(stream_);
if (ret != ACL_ERROR_NONE){
ERROR_LOG("aclrtSynchronizeStream failed, ret=%d", ret);
return FAILED;
}
// 解码结束后 释放输入数据内存
(void)acldvppFree(picDevBuffer);
picDevBuffer = nullptr;
// 解码数据写入磁盘
void *dvppOutputBuffer = nullptr;
int dvppOutputSize;
GetOutput(&dvppOutputBuffer, dvppOutputSize);
std::string dvppOutputfileNameCur = dvppOutputfileName + std::to_string(index);
ret = SaveDvppOutputData(dvppOutputfileNameCur.c_str(), dvppOutputBuffer, dvppOutputSize);
if (ret != SUCCESS){
ERROR_LOG("save dvpp output data failed");
}
// 销毁本张图片描述信息
acldvppDestroyPicDesc(decodeOutputDesc_);
}
// 销毁解码channel
DestroyDecodeResource();
// 销毁运行资源
DestroyResource();
INFO_LOG("execute sample sucecss");
return SUCCESS;
}
// 函数定义
void CreatResource(){
aclrtSetDevice(deviceId_);
INFO_LOG("open device %d success", deviceId_);
aclrtCreateContext(&context_, deviceId_);
INFO_LOG("open context success");
aclrtCreateStream(&stream_);
INFO_LOG("open stream success");
}
void CreateDecodeResource(){
dvppChannelDesc_ = acldvppCreateChannelDesc();
acldvppCreateChannel(dvppChannelDesc_); // Desc里就有了一个创建好的Channel了
INFO_LOG("dvpp init resource success");
}
char* ReadBinFile(std::string fileName, uint32_t &fileSize){
struct stat sBuf;
int fileStatus = stat(fileName.data(), &sBuf);
if (fileStatus == -1) {
ERROR_LOG("failed to get file");
return nullptr;
}
if (S_ISREG(sBuf.st_mode) == 0) {
ERROR_LOG("%s is not a file, please enter a file", fileName.c_str());
return nullptr;
}
std::ifstream binFile(fileName, std::ifstream::binary);
if (binFile.is_open() == false) {
ERROR_LOG("open file %s failed", fileName.c_str());
return nullptr;
}
binFile.seekg(0, binFile.end);
uint32_t binFileBufferLen = binFile.tellg();
if (binFileBufferLen == 0) {
ERROR_LOG("binfile is empty, filename is %s", fileName.c_str());
binFile.close();
return nullptr;
}
binFile.seekg(0, binFile.beg);
void* binFileBufferData = nullptr;
aclError ret = ACL_ERROR_NONE;
if (!g_isDevice) {
ret = aclrtMallocHost(&binFileBufferData, binFileBufferLen);
if (binFileBufferData == nullptr) {
ERROR_LOG("malloc binFileBufferData failed");
binFile.close();
return nullptr;
}
} else {
ret = aclrtMalloc(&binFileBufferData, binFileBufferLen, ACL_MEM_MALLOC_NORMAL_ONLY);
if (ret != ACL_ERROR_NONE) {
ERROR_LOG("malloc device buffer failed. size is %u", binFileBufferLen);
binFile.close();
return nullptr;
}
}
binFile.read(static_cast<char *>(binFileBufferData), binFileBufferLen);
binFile.close();
fileSize = binFileBufferLen;
return binFileBufferData;
}
void* GetDeviceBufferOfPicture(const PicDesc &picDesc, uint32_t &devPicBufferSize){
if (picDesc.picName.empty(){
ERROR_LOG ( "picture file name is empty") ;
return nullptr ;
}
// 从硬盘中读取
uint32_t inputHostBuffSize = 0 ;
// 将输入图片内存返回
char* inputHostBuff = ReadBinFile(picDesc.picName, inputHostBuffSize);
if (inputHostBuff == nullptr){
return nullptr;
}
void *inBufferDev = nullptr ;
uint32_t inBufferSize = inputHostBuffSize;
aclError ret = acldvppMalloc(&inBufferDev, inBufferSize); // 申请DVPP内存,用于存放输入JPEG码流
if (ret != ACL_ERROR_NONE) {
ERROR_LOG("malloc device buffer failed. size is %u", inBufferSize);
delete[] inputHostBuff;
return nullptr;
}
// 将输入数据复制到DVPP申请的内存中
ret = aclrtMemcpy(inBufferDev,inBufferSize,inputHostBuff,inputHostBuffSize, ACL_MEMCPY_HOST_TO_DEVICE);
if (ret!=ACL_ERROR_NONE){
ERROR_LOG ("memcpy failed, device buffer size is %u, input host buffer size is u", inBufferSize, inputHostBuffSize) ;
acldvppFree (inBufferDev);
delete[] inputHostBuff;
return nullptr ;
}
// 用DVPP自带接口计算解码后数据内存大小
ret = acldvppJpegPredictDecSize(inputHostBuff, inputHostBuffSize, PIXEL_FORMAT_YUV_SEMIPLANAR_420, &decodeDataSize_);
if (ret != ACL_ERROR_NONE){
ERROR_LOG("acldvppJpegPredictDecSize failed, ret = %d", ret);
}
// 计算H, W, C
uint32_t picWidth=0;
uint32_t picHeight=0;
int32_t picComponents=0;
ret = acldvppJpegGetImageInfo(inputHostBuff, inputHostBuffSize, &picWidth, &picHeight, &picComponents);
if (ret != ACL_ERROR_NONE){
ERROR_LOG("acldvppJpegGetImageInfo failed, ret=%d", ret);
}
else{
INFO_LOG("Picture: %s, widthL %d, heights: %d, channel: %d", picDesc.picName.c_str(), picWidth, picHeight, picComponents);
}
delete[] inputHostBuff;
devPicBufferSize = inBufferSize;
return inBufferDev;
}
// 将准备好的输入传递给内置对象(头文件中定义的)
void SetInput(void *inDevBuffer, int inDevBufferSize, int inputWidth, int inputHeight){
inDevBuffer_ = inDevBuffer;
inDevBufferSize_ = inDevBufferSize;
inputWidth_ = inputWidth;
inputHeight_ = inputHeight;
}
void GetOutput(void **outputBuffer, int &outputSize){
*outputBuffer = decodeOutDevBuffer_;
outputSize = decodeDataSize_;
decodeOutDevBuffer_ = nullptr;
decodeDataSize_ = 0;
}
// 写入硬盘
Result SaveDvppOutputData(const char *fileName, const void *devPtr, uint32_t dataSize){
// 将输出数据从Device拷到Host
void *hostPtr = nullptr;
aclError aclRet = aclrtMal1ocHost(&hostPtr, dataSize);
if (aclRet != ACL_ERROR_NONE){
ERROR_LOG("malloc host data buffer failed,aclRet is %d", aclRet);
return FAILED;
}
aclRet = aclrtMemcpy(hostPtr, dataSize, devPtr, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
if (aclRet != ACL_ERROR_NONE){
ERROR_LOG("dvpp output memcpy to host failed, aclRet is %d", aclRet);
(void)aclrtFreeHost(hostPtr);
return FAILED;
}
// 打开文件指针
FILE *outFileFp = fopen(fileName, "wb+");
if (nullptr == outFi1eFp){
ERROR_LOG("fopen out file %s failed. ", fileName);
(void) ac1rtFreeHost(hostPtr) ;
return FAILED;
}
// 内存写入
size_t writeSize = fwrite(hostPtr, sizeof(char),dataSize,outFileFp);
if(writeSize != dataSize){
ERROR_LOG("need write %u bytes to %s,but only write 号zu bytes. ",
dataSize, fileName, writeSize);
(void)aclrtFreeHost(hostPtr);
return FAILED ;
}
(void)aclrtFreeHost(hostPtr);
fflush(outFileFp);
fclose(outFileFp);
return SUCCESS;
}
void DestroyDecodeResource()
{
if (decodeOutputDesc_ != nullptr){
acldvppDestroyChannel(dvppChannelDesc_); // 销毁channel
acldvppDestroyChannelDesc(dvppChannelDesc_); // 销毁desc
decodeOutputDesc_ = nullptr;
}
}
void DestroyResource (){
aclError ret;
if (stream_ != nullptr) {
ret =aclrtDestroyStream(stream_);
if (ret != ACL_ERROR_NONE) {
ERROR_LOG("destroy stream failed");
}
stream_ = nullptr;
}
INFO_LOG("end to destroy stream");
if (context_ != nullptr){
ret =aclrtDestroyContext(context_) ;
if (ret != ACL_ERROR_NONE){
ERROR_LOG("destroy context failed");
}
context_ = nullptr;
}
INFO_LOG("end to destroy context");
ret = aclrtResetDevice(deviceId_);
if (ret !=ACL_ERROR_NONE){
ERROR_LOG("reset device failed");
}
INFO_LOG("end to reset device is %d", deviceId_);
ret = aclFinalize();
if (ret !=ACL_ERROR_NONE){
ERROR_LOG("finalize acl failed");
}
INFO_LOG("end to finalize acl");
}
3. 编译运行
使用dvpp时CMakeList有些不一样
# Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
# CMake lowest version requirement
cmake_minimum_required(VERSION 3.5.1)
# project information
project(ACL_HELLO_WORLD)
# Compile options
add_compile_options(-std=c++11)
add_definitions(-DENABLE_DVPP_INTERFACE)
# 指定生成路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../out")
set(CMAKE_CXX_FLAGS_DEBUG "-fPIC -O0 -g -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-fPIC -O2 -Wall")
set(INC_PATH $ENV{DDK_PATH})
if (NOT DEFINED ENV{DDK_PATH})
if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
set(INC_PATH "C:/Program Files/HuaWei/Ascend")
else ()
set(INC_PATH "/usr/local/Ascend")
endif ()
message(STATUS "set default INC_PATH: ${INC_PATH}")
else ()
message(STATUS "env INC_PATH: ${INC_PATH}")
endif ()
set(LIB_PATH $ENV{NPU_HOST_LIB})
# Dynamic libraries in the stub directory can only be used for compilation
if (NOT DEFINED ENV{NPU_HOST_LIB})
if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
set(LIB_PATH "C:/Program Files/HuaWei/Ascend/Acllib/lib64")
else ()
set(LIB_PATH "/usr/local/Ascend/acllib/lib64/stub/")
endif ()
message(STATUS "set default LIB_PATH: ${LIB_PATH}")
else ()
message(STATUS "env LIB_PATH: ${LIB_PATH}")
endif ()
# Header path
# 引用除acl.h 需要在此添加头文件
include_directories(
${INC_PATH}/acllib/include/
)
if(target STREQUAL "Simulator_Function")
add_compile_options(-DFUNC_SIM)
endif()
# add host lib path
link_directories(
${LIB_PATH}
)
# 可能变化的
# 想要编译的源文件
add_executable(jpegd # 指生成的可执行文件
jpged.cpp) # 指待编译的源文件
target_link_libraries(jpegd
ascendcl acl_cblas acl_dvpp stdc++ )
install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
cmake -> make -> touch -> main
2.4 抠图和缩放
由VPC模块实现功能。可以实现抠图、缩放、叠加和拼接功能。
- VPC要求输入宽高对齐
YUV420SP宽高要求对齐至16。
2.4.1 缩放接口
acldvppResizeConfig *acldvppCreateResizeConfig()
创建图片缩放配置数据。
acldvppDestroyResizeConfig(acldvppResizeConfig *resizeConfig)
:销毁缩放配置