【CANN训练营】【2022第二季】【进阶班】 hw37497260 图像处理应用开发(解码,缩放,编码功能串接)

举报
Heartt 发表于 2022/08/23 19:36:56 2022/08/23
【摘要】 ​ 一、基本概念二、功能、流程及接口调用1.JPEG图片解码(JPEGD)功能:将.jpg、.jpeg、.JPG、.JPEG图片解码成YUV格式图片流程:2. JPEG图片编码(JPEGE):功能:将YUV格式图片编码成.jpg图片,用于直接展示等场景。流程:3. 视觉与处理模块(VPC)流程:4.接口调用:调用acldvppCreateChannel接口创建图片数据处理的通道。 创建图片数...

 一、基本概念

二、功能、流程及接口调用

1.JPEG图片解码(JPEGD)

功能:将.jpg、.jpeg、.JPG、.JPEG图片解码成YUV格式图片

流程:

2. JPEG图片编码(JPEGE):

功能:将YUV格式图片编码成.jpg图片,用于直接展示等场景。

流程

3. 视觉与处理模块(VPC)

流程:

4.接口调用:

  • 调用acldvppCreateChannel接口创建图片数据处理的通道。 创建图片数据处理的通道前,需先调用acldvppCreateChannelDesc接口创建通道描述信息。
  • 调用acldvppCreateResizeConfig接口创建缩放配置。
  • 调用acldvppCreateJpegeConfig接口创建图片编码配置数据。
  • 若需要申请Device上的内存存放输入或输出数据,需调用acldvppMalloc申请内存。 在申请输出内存前,可根据存放JPEG图片数据的内存,调用acldvppJpegPredictDecSize接口预估JPEG图片解码后所需的输出内存的大小。
  • 调用acldvppJpegDecodeAsync异步接口进行解码。 对于异步接口,还需调用aclrtSynchronizeStream接口阻塞程序运行,直到指定Stream中的所有任务都完成。
  • 调用acldvppVpcResizeAsync异步接口,将输入图片缩放到输出图片大小。缩放后输出图片内存根据YUV420SP格式计算,计算公式:对齐后的宽 * 对齐后的高 * 3 / 2;对于异步接口,还需调用aclrtSynchronizeStream接口阻塞程序运行,直到指定Stream中的所有任务都完成。
  • 调用acldvppJpegEncodeAsync异步接口进行编码。 对于异步接口,还需调用aclrtSynchronizeStream接口阻塞程序运行,直到指定Stream中的所有任务都完成。
  • 调用acldvppFree接口释放输入、输出内存。
  • 调用acldvppDestroyResizeConfig接口销毁缩放配置。
  • 调用acldvppDestroyJpegeConfig接口销毁图片编码配置数据。
  • 调用acldvppDestroyChannel接口销毁图片数据处理的通道。销毁图片数据处理的通道后,再调用acldvppDestroyChannelDesc接口销毁通道描述信息。


三、思路总结

要实现功能串接,内存复用,申请内存需求:输入图片读入,解码后的图片,缩放后图片,解码后图片。其中解码后的图片和缩放后的图片需要使用acldvppCreatePicDesc()接口创建输出图片描述信息。解码后,可以释放读入图片内存;执行缩放处理后,可以释放解码后图片内存。

流程:

  1. 运行管理资源申请:device,context,stream创建
  2. 将图片读入内存
  3. 创建图片数据处理通道描述信息及通道
  4. Device侧申请内存存放解码后图片,创建图片描述信息及图片描述属性设置
  5. 执行异步解码,同步等待
  6. 创建缩放配置
  7. Device侧申请内存存放缩放后图片,创建图片描述信息及图片描述属性设置
  8. 执行异步缩放处理,同步等待
  9. 创建图片编码配置数据
  10. 申请内存存放编码后图片
  11. 执行异步编码,同步等待
  12. 保存编码后图片
  13. 释放内存及资源

四、代码分析

1.为了能处理不同路径下的图片,并且得到不同大小的输出图片,将输入图片路径、输出图片的宽、输出图片的高作为main函数的参数:

string image_path = string(argv[1]);
int outw,outh;
outw = atoi(argv[2]);
outh = atoi(argv[3]);
2.初始化及运行管理资源申请
//1.acl init
const char *aclConfigPath = "../src/acl.json";
aclInit(aclConfigPath);
INFO_LOG("acl init success");
//2.create Device,Context,Stream
aclrtSetDevice(deviceId_);
INFO_LOG("open device %d success", deviceId_);
// create context (set current)
aclrtCreateContext(&context_, deviceId_);
// create stream
aclrtCreateStream(&stream_);
aclrtGetRunMode(&runMode);
INFO_LOG("create stream success");

3.将图片读入内存,使用如下函数实现

void* GetDeviceBufferOfPicture(PicDesc &picDesc, uint32_t &devPicBufferSize)
{
    if (picDesc.picName.empty()) {
        ERROR_LOG("picture file name is empty");
        return nullptr;
    }

    FILE *fp = fopen(picDesc.picName.c_str(), "rb");
    if (fp == nullptr) {
        ERROR_LOG("open file %s failed", picDesc.picName.c_str());
        return nullptr;
    }

    fseek(fp, 0, SEEK_END);
    uint32_t fileLen = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    uint32_t inputBuffSize = fileLen;

    char* inputBuff = new(std::nothrow) char[inputBuffSize];
    size_t readSize = fread(inputBuff, sizeof(char), inputBuffSize, fp);
    if (readSize < inputBuffSize) {
        ERROR_LOG("need read file %s %u bytes, but only %zu readed",
        picDesc.picName.c_str(), inputBuffSize, readSize);
        delete[] inputBuff;
		fclose(fp); 
        return nullptr;
    }

    acldvppJpegFormat format;
    
    aclError aclRet = acldvppJpegGetImageInfoV2(inputBuff, inputBuffSize, &picDesc.width, &picDesc.height,
                                                nullptr, &format);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("get jpeg image info failed, errorCode is %d", static_cast(aclRet));
        delete[] inputBuff;
		fclose(fp); 
        return nullptr;
    }

    INFO_LOG("get jpeg image info successed, width=%d, height=%d, format=%d, jpegDecodeSize=%d", picDesc.width, picDesc.height, format, picDesc.jpegDecodeSize);
    
    // when you run, from the output, we can see that the original format is ACL_JPEG_CSS_420, 
    // so it can be decoded as PIXEL_FORMAT_YUV_SEMIPLANAR_420 or PIXEL_FORMAT_YVU_SEMIPLANAR_420
    aclRet = acldvppJpegPredictDecSize(inputBuff, inputBuffSize, PIXEL_FORMAT_YUV_SEMIPLANAR_420, &picDesc.jpegDecodeSize);    
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("get jpeg decode size failed, errorCode is %d", static_cast(aclRet));
        delete[] inputBuff;
		fclose(fp); 
        return nullptr;
    }    

    void *inBufferDev = nullptr;
    aclError ret = acldvppMalloc(&inBufferDev, inputBuffSize);
    if (ret !=  ACL_SUCCESS) {
        delete[] inputBuff;
        ERROR_LOG("malloc device data buffer failed, aclRet is %d", ret);
		fclose(fp); 
        return nullptr;
    }

    if (runMode == ACL_HOST) {
        ret = aclrtMemcpy(inBufferDev, inputBuffSize, inputBuff, inputBuffSize, ACL_MEMCPY_HOST_TO_DEVICE);
    }
    else {
        ret = aclrtMemcpy(inBufferDev, inputBuffSize, inputBuff, inputBuffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
    }
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("memcpy failed. Input host buffer size is %u",
        inputBuffSize);
        acldvppFree(inBufferDev);
        delete[] inputBuff;
		fclose(fp); 
        return nullptr;
    }

    delete[] inputBuff;
    devPicBufferSize = inputBuffSize;
	fclose(fp); 
    return inBufferDev;
}

4.创建图片数据处理通道描述信息及通道

dvppChannelDesc_ = acldvppCreateChannelDesc();
acldvppCreateChannel(dvppChannelDesc_);
INFO_LOG("dvpp init resource success");

5.计算图片内存

uint32_t decodeOutWidthStride = AlignmentHelper(inputWidth_, 128);
uint32_t decodeOutHeightStride = AlignmentHelper(inputHeight_, 16);
uint32_t decodeOutBufferSize = decodeOutWidthStride * decodeOutHeightStride * 3 / 2; // yuv format size

对齐后高宽通过AlignmentHelper函数计算

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

6.申请解码后图片内存

ret = acldvppMalloc(&decodeOutDevBuffer_, testPic.jpegDecodeSize);
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("acldvppMalloc jpegOutBufferDev failed, ret = %d", ret);
        return FAILED;
    }

7.调用acldvppCreatePicDesc接口创建图片描述信息。调用acldvppSetPicDesc系列接口设置解码后图片的内存地址、内存大小、格式、widthStride、heightStride。

decodeOutputDesc_ = acldvppCreatePicDesc();
if (decodeOutputDesc_ == nullptr) {
    ERROR_LOG("acldvppCreatePicDesc decodeOutputDesc failed");
    return FAILED;
}

acldvppSetPicDescData(decodeOutputDesc_, decodeOutDevBuffer_);
// here the format shoud be same with the value you set when you get decodeOutBufferSize from
acldvppSetPicDescFormat(decodeOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420); 
acldvppSetPicDescWidth(decodeOutputDesc_, inputWidth_);
acldvppSetPicDescHeight(decodeOutputDesc_, inputHeight_);
acldvppSetPicDescWidthStride(decodeOutputDesc_, decodeOutWidthStride);
acldvppSetPicDescHeightStride(decodeOutputDesc_, decodeOutHeightStride);
acldvppSetPicDescSize(decodeOutputDesc_, testPic.jpegDecodeSize);

8.执行异步解码,同步等待,释放读入图片内存

ret = acldvppJpegDecodeAsync(dvppChannelDesc_, picDevBuffer, devPicBufferSize,decodeOutputDesc_, stream_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("acldvppJpegDecodeAsync failed, ret = %d", ret);
    return FAILED;
}

ret = aclrtSynchronizeStream(stream_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclrtSynchronizeStream failed");
    return FAILED;
}

destroy_buffer(picDevBuffer);
destory_PicDesc(decodeOutputDesc_);

9.创建缩放配置

acldvppResizeConfig *resizeConfig_ = acldvppCreateResizeConfig();

10.计算缩放后图片内存大小

int resizeOutWidthStride = AlignmentHelper(outw, 16);
int resizeOutHeightStride = AlignmentHelper(outh, 2);
uint32_t vpcOutBufferSize_ = resizeOutWidthStride * resizeOutHeightStride * sizeAlignment / sizeNum;

11.申请缩放后图片内存

void *vpcOutBufferDev_ = nullptr;
acldvppMalloc(&vpcOutBufferDev_, vpcOutBufferSize_);

12.调用acldvppCreatePicDesc接口创建图片描述信息。调用acldvppSetPicDesc系列接口设置缩放后图片的内存地址、内存大小、格式、widthStride、heightStride。

vpcOutputDesc_ = acldvppCreatePicDesc();
if (vpcOutputDesc_ == nullptr) {
    ERROR_LOG("acldvppCreatePicDesc decodeOutputDesc failed");
    return FAILED;
}
acldvppSetPicDescData(vpcOutputDesc_, vpcOutBufferDev_);
// the format should be under the VPC constraints(only support the following 2):
// PIXEL_FORMAT_YUV_SEMIPLANAR_420 = 1
// PIXEL_FORMAT_YVU_SEMIPLANAR_420 = 2
acldvppSetPicDescFormat(vpcOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420);
acldvppSetPicDescWidth(vpcOutputDesc_, outw);
acldvppSetPicDescHeight(vpcOutputDesc_, outh);
acldvppSetPicDescWidthStride(vpcOutputDesc_, resizeOutWidthStride);
acldvppSetPicDescHeightStride(vpcOutputDesc_, resizeOutHeightStride);
acldvppSetPicDescSize(vpcOutputDesc_, vpcOutBufferSize_);

13.执行异步缩放处理,同步等待,释放解码后图片内存

ret = acldvppVpcResizeAsync(dvppChannelDesc_, decodeOutputDesc_,
        vpcOutputDesc_, resizeConfig_, stream_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("acldvppVpcResizeAsync failed, ret = %d", ret);
    return FAILED;
}
    
aclrtSynchronizeStream(stream_);
if (ret != ACL_SUCCESS) {
    ERROR_LOG("aclrtSynchronizeStream failed");
    return FAILED;
}

destroy_buffer(decodeOutDevBuffer_);

14.编码配置

uint32_t encodeLevel = 100; // default optimal level (0-100)
jpegeConfig_ = acldvppCreateJpegeConfig();
INFO_LOG("Call acldvppCreateJpegeConfig success");
acldvppSetJpegeConfigLevel(jpegeConfig_, encodeLevel);

15.申请编码后图片内存

acldvppJpegPredictEncSize(vpcOutputDesc_, jpegeConfig_, &encode_outbuffer_size_);
aclError aclRet = acldvppMalloc(&encode_out_buffer_dev_, encode_outbuffer_size_);
if (aclRet != ACL_SUCCESS) {
    ERROR_LOG("malloc encodeOutBufferDev_ failed, aclRet is %d", aclRet);
    return FAILED;
}

16.执行异步编码,同步等待

aclRet = acldvppJpegEncodeAsync(dvppChannelDesc_, vpcOutputDesc_, encode_out_buffer_dev_,
    &encode_outbuffer_size_, jpegeConfig_, stream_);
if (aclRet != ACL_SUCCESS) {
    ERROR_LOG("acldvppJpegEncodeAsync failed, aclRet = %d", aclRet);
    return FAILED;
}
INFO_LOG("Call acldvppJpegEncodeAsync success");
aclRet = aclrtSynchronizeStream(stream_);
if (aclRet != ACL_SUCCESS) {
    ERROR_LOG("encode aclrtSynchronizeStream failed, aclRet = %d", aclRet);
    return FAILED;
}

17.输出保存路径设置

int dir_tail_index = image_path.find("/data");
std::string outfile_dir = image_path.substr(0, dir_tail_index) + "/" + "out/output/";
std::string outfile_path = outfile_dir + image_path.substr(dir_tail_index+5+1, image_path.rfind(".yuv")-dir_tail_index-5-1) + "_jpege_" + std::to_string(inputWidth_) + "_" + std::to_string(inputHeight_) + ".jpg";   
INFO_LOG("outfile_path=%s", outfile_path.c_str());

18.保存编码后图片,使用以下函数实现

Result save_dvpp_outputdata(const char *fileName, const void *devPtr, uint32_t dataSize)
{
    FILE * outFileFp = fopen(fileName, "wb+");
    if (nullptr == outFileFp) {
        ERROR_LOG("fopen out file %s failed.", fileName);
        return FAILED;
    }
    if (runMode == ACL_HOST) {
        void* hostPtr = nullptr;
        aclError aclRet = aclrtMallocHost(&hostPtr, dataSize);
        if (aclRet != ACL_SUCCESS) {
            ERROR_LOG("malloc host data buffer failed, aclRet is %d", aclRet);
            fclose(outFileFp);
            return FAILED;
        }

        aclRet = aclrtMemcpy(hostPtr, dataSize, devPtr, dataSize, ACL_MEMCPY_DEVICE_TO_HOST);
        if (aclRet != ACL_SUCCESS) {
            ERROR_LOG("dvpp output memcpy to host failed, aclRet is %d", aclRet);
            (void)aclrtFreeHost(hostPtr);
            fclose(outFileFp);
            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);
            fclose(outFileFp);
            return FAILED;
        }
        (void)aclrtFreeHost(hostPtr);
    }
    else {
        size_t writeSize = fwrite(devPtr, sizeof(char), dataSize, outFileFp);
        if (writeSize != dataSize) {
            ERROR_LOG("need write %u bytes to %s, but only write %zu bytes.",
            dataSize, fileName, writeSize);

            fclose(outFileFp);
            return FAILED;
        }
    }

    fflush(outFileFp);
    fclose(outFileFp);
    return SUCCESS;
}

19.内存及资源释放

destory_PicDesc(vpcOutputDesc_);
destroy_buffer(vpcOutBufferDev_);
destroy_buffer(encode_out_buffer_dev_ );
destory_Resizeconfig(resizeConfig_);
destory_Jpegeconfig(jpegeConfig_);
acldvppDestroyChannel(dvppChannelDesc_);
destory_channelDesc(dvppChannelDesc_);
destroy_resource();

其中:

void destroy_resource()
{
    aclError ret;
    if (stream_ != nullptr) {
        ret = aclrtDestroyStream(stream_);
        if (ret != ACL_SUCCESS) {
            ERROR_LOG("destroy stream failed");
        }
        stream_ = nullptr;
    }
    INFO_LOG("End to destroy stream");

    if (context_ != nullptr) {
        ret = aclrtDestroyContext(context_);
        if (ret != ACL_SUCCESS) {
            ERROR_LOG("destroy context failed");
        }
        context_ = nullptr;
    }
    INFO_LOG("End to destroy context");

    ret = aclrtResetDevice(deviceId_);
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("reset device failed");
    }
    INFO_LOG("End to reset device is %d", deviceId_);

    ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
        ERROR_LOG("finalize acl failed");
    }
    INFO_LOG("End to finalize acl");
}
void destory_PicDesc(acldvppPicDesc *picDesc)
{
    if (picDesc != nullptr) {
        (void)acldvppDestroyPicDesc(picDesc);
        picDesc = nullptr;
    }
}

其他函数类似,调用相关接口释放资源

注:先调用acldvppDestroyChannel接口销毁图片数据处理的通道。销毁图片数据处理的通道后,再调用acldvppDestroyChannelDesc接口销毁通道描述信息。

五、运行过程及结果

1. 购买ECS云服务器

2. 通过MobaXterm访问购买的云服务器

3. 切换到HwHiAiUser用户

4. 获取源码:命令行运行:git clone https://gitee.com/ascend/samples.git

可以看到目录中有samples文件夹

5. 上传准备好的图像处理应用文件夹

6. 编译应用:

进入scrpts目录下:bash sample_build.sh

同时测试图片已被下载到data路径下

原图:分辨率为1024*683

7. 运行应用

bash sample_run.sh

输出图片已保存到out/output路径下

输出图片:分辨率为224*224

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。