# VPC图片处理典型功能

VPC（Vision Preprocessing Core）负责图像处理功能，支持对图片做抠图、缩放、格式转换等操作。关于VPC功能的详细介绍请参见功能说明，关于VPC功能对输入、输出的约束要求，请参见约束说明。

本节以抠图、缩放为例说明VPC图像处理时的接口调用流程，同时配合以下典型功能的示例代码辅助理解该接口调用流程：

-   图片缩放示例代码

-   抠图示例代码


#### 典型功能接口调用流程（以缩放为例）

开发应用时，如果涉及抠图、缩放等图片处理，则应用程序中必须包含图片处理的代码逻辑，关于图片处理的接口调用流程，请先参见AscendCL接口调用流程了解整体流程，再查看本节中的流程说明。


图1 主要接口调用流程

当前系统支持对输入图片做抠图、缩放等处理，关键接口的说明如下：

1. 调用  hi_mpi_sys_init
接口进行媒体数据处理系统初始化。
2. 调用  hi_mpi_vpc_create_chn
接口创建通道。
3. 调用  hi_mpi_dvpp_malloc
接口申请Device上的内存，存放输入或输出数据。
  Atlas 200/500 A2推理产品上，还支持使用aclrtMalloc接口申请内存。

  调用hi_mpi_dvpp_malloc接口申请的内存为媒体数据处理的专用内存，但专用内存的地址空间有限，若关注内存规划或内存资源有限时，建议调用aclrtMalloc接口申请内存。

4. 执行抠图、缩放等，此步骤以缩放为例说明，其它功能（例如格式转换、金字塔等）请参见VPC功能下的接口说明。
调用hi_mpi_vpc_resize接口，对图片进行缩放。hi_mpi_vpc_resize接口是异步接口，调用该接口成功仅表示任务下发成功，还需要调用hi_mpi_vpc_get_process_result接口等待任务完成。

  - 可以跟    hi_mpi_vpc_resize
接口在同一个线程中调用    hi_mpi_vpc_get_process_result
接口，也可以新起一个线程调用    hi_mpi_vpc_get_process_result
接口，后者多线程并行，提高效率，但用户需自行实现线程间同步。
  - 在实现抠图、缩放等功能时，通过将输入图片和输出图片的格式设置成不同的，达到转换图片格式的目的。
5. 调用  hi_mpi_dvpp_free
接口释放输入、输出内存。
  Atlas 200/500 A2推理产品上，若使用aclrtMalloc接口申请内存，则需使用aclrtFree接口释放内存。

6. 调用  hi_mpi_vpc_destroy_chn
接口销毁通道。
7. 调用  hi_mpi_sys_exit
接口进行媒体数据处理系统去初始化。


#### 图片缩放示例代码

调用接口后，需增加异常处理的分支，并记录报错日志、提示日志，此处不一一列举。以下是关键步骤的代码示例，不可以直接拷贝编译运行，仅供参考。

```
// 1.AscendCL初始化
aclRet = aclInit(nullptr);

// 2.运行管理资源申请（依次申请Device、Context）
aclrtContext g_context;
aclRet = aclrtSetDevice(0);
aclRet = aclrtCreateContext(&g_context, 0);
// 获取软件栈的运行模式，不同运行模式影响后续的接口调用流程（例如是否进行数据传输等）
aclrtRunMode runMode;
aclError aclRet = aclrtGetRunMode(&runMode);

// 3.初始化媒体数据处理系统
int32_t ret = hi_mpi_sys_init();

// 4.创建通道
hi_vpc_chn chnId;
hi_vpc_chn_attr stChnAttr;
ret = hi_mpi_vpc_sys_create_chn(&chnId, &stChnAttr);

// 5.执行缩放
// 5.1 构造存放输入图片信息的结构体
hi_vpc_pic_info inputPic;
inputPic.picture_width = 1920;
inputPic.picture_height = 1080;
inputPic.picture_format = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420;
inputPic.picture_width_stride = 1920;
inputPic.picture_height_stride = 1080;
inputPic.picture_buffer_size = inputPic.picture_width_stride * inputPic.picture_height_stride * 3 / 2;

// 5.2 准备输入图片数据
// 申请Device内存，用于媒体数据处理
ret = hi_mpi_dvpp_malloc(0, &inputPic.picture_address, inputPic.picture_buffer_size);

// 如果运行模式为ACL_HOST，则需要申请Host内存，将输入图片数据读入Host内存，再通过aclrtMemcpy接口将Host的图片数据传输到Device，数据传输完成后，需及时释放Host内存；否则直接将输入图片数据读入Device内存
if (runMode == ACL_HOST) {
    void* inputAddr = nullptr;
    // 申请Host内存
    aclRet = aclrtMallocHost(&inputAddr, inputPic.picture_buffer_size);
    // 将输入图片读入内存中，该自定义函数ReadPicFile由用户实现
    ReadPicFile(picName, inputAddr, inputPic.picture_buffer_size);
    // 数据传输
    aclRet = aclrtMemcpy(inputPic.picture_address, inputPic.picture_buffer_size, inputAddr,
                         inputPic.picture_buffer_size, ACL_MEMCPY_HOST_TO_DEVICE);
    // 完成数据传输后，需及时释放内存
    aclrtFreeHost(inputAddr);
    inputAddr = nullptr;
} 
else {
    // 将输入图片读入内存中，该自定义函数ReadPicFile由用户实现
    ReadPicFile(picName, inputPic.picture_address, inputPic.picture_buffer_size);
    }

// 5.3 构造存放输出图片信息的结构体
hi_vpc_pic_info outputPic;
outputPic.picture_width = 960;
outputPic.picture_height  = 540;
outputPic.picture_format = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420;
outputPic.picture_width_stride = 960;
outputPic.picture_height_stride = 540;
outputPic.picture_buffer_size = outputPic.picture_width_stride * outputPic.picture_height_stride * 3 / 2;
ret = hi_mpi_dvpp_malloc(0, &outputPic.picture_address, outputPic.picture_buffer_size);

// 初始化内存
if (runMode == ACL_HOST) {
    aclRet = aclrtMemset(outputPic.picture_address, outputPic.picture_buffer_size, 0, outputPic.picture_buffer_size);
} else {
     memset(outputPic.picture_address, 0, outputPic.picture_buffer_size);
}

// 5.4 调用缩放接口
uint32_t taskID = 0;
ret = hi_mpi_vpc_resize(chnId, &inputPic, &outputPic, 0, 0, 0, &taskID, -1);

// 5.5 等待任务处理结束，任务处理结束后，输出图片数据在outputPic.picture_address指向的内存中
uint32_t taskIDResult = taskID;
ret = hi_mpi_vpc_get_process_result(chnId, taskIDResult, -1);

// 5.6 如果运行模式为ACL_HOST，且Host上需要展示VPC输出的图片数据，则需要申请Host内存，通过aclrtMemcpy接口将Device的输出图片数据传输到Host；如果Host上不需要展示VPC输出的图片数据，则VPC的输出图片数据可以直接作为模型推理的输入
if (g_runMode == ACL_HOST) {
    hi_vpc_pic_info outputPicHost = outputPic;
    aclRet = aclrtMallocHost(&outputPicHost.picture_address, outputPic.picture_buffer_size);
    aclRet = aclrtMemcpy(outputPicHost.picture_address, outputPic.picture_buffer_size, outputPic.picture_address,outputPic.picture_buffer_size, ACL_MEMCPY_DEVICE_TO_HOST);
    // ......
    // Host侧的数据使用完成后，释放Host内存
    aclrtFreeHost(outputPicHost.picture_address);
    outputPicHost.picture_address = nullptr;
} else {
    // 可以直接使用VPC的输出图片数据，在outputPic.picture_address指向的内存中
    // TODO：推理相关的代码逻辑
}

// 5.7 释放输入、输出内存
ret = hi_mpi_dvpp_free(inputPic.picture_address);
ret = hi_mpi_dvpp_free(outputPic.picture_address);

// 6.销毁通道
ret = hi_mpi_vpc_destroy_chn(chnId);

// 7. 媒体数据处理系统去初始化
ret = hi_mpi_sys_exit();

// 8. 释放运行管理资源（依次释放Context、Device）
aclRet = aclrtDestroyContext(g_context);
aclRet = aclrtResetDevice(0);

// 9.AscendCL去初始化
aclRet = aclFinalize();

// ....
```


#### 抠图示例代码


调用接口后，需增加异常处理的分支，并记录报错日志、提示日志，此处不一一列举。以下是关键步骤的代码示例，不可以直接拷贝编译运行，仅供参考。

```
// 1.AscendCL初始化
aclRet =
aclInit
(nullptr);
// 2.运行管理资源申请（依次申请Device、Context）
aclrtContext g_context;
aclRet =
aclrtSetDevice
(0);
aclRet =
aclrtCreateContext
(&g_context, 0);
获取软件栈的运行模式，不同运行模式影响后续的接口调用流程（例如是否进行数据传输等）
aclrtRunMode runMode;
aclError aclRet =
aclrtGetRunMode
(&runMode);
// 3.初始化媒体数据处理系统
int32_t ret =
hi_mpi_sys_init
();
// 4.创建通道
hi_vpc_chn chnId;
hi_vpc_chn_attr stChnAttr;
ret =
hi_mpi_vpc_sys_create_chn
(&chnId, &stChnAttr);
// 5.执行抠图
// 5.1 构造存放输入图片信息的结构体
hi_vpc_pic_info inputPic;
inputPic.picture_width = 1920;
inputPic.picture_height = 1080;
inputPic.picture_format = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420;
inputPic.picture_width_stride = 1920;
inputPic.picture_height_stride = 1080;
inputPic.picture_buffer_size = inputPic.picture_width_stride * inputPic.picture_height_stride * 3 / 2;
// 5.2 准备输入图片数据
// 申请Device内存，用于媒体数据处理
ret =
hi_mpi_dvpp_malloc
(0, &inputPic.picture_address, inputPic.picture_buffer_size);
// 如果运行模式为ACL_HOST，则需要申请Host内存，将输入图片数据读入Host内存，再通过aclrtMemcpy接口将Host的图片数据传输到Device，数据传输完成后，需及时释放Host内存；否则直接将输入图片数据读入Device内存
if (runMode == ACL_HOST) {
void* inputAddr = nullptr;
// 申请Host内存
aclRet =
aclrtMallocHost
(&inputAddr, inputPic.picture_buffer_size);
// 将输入图片读入内存中，该自定义函数ReadPicFile由用户实现
ReadPicFile(picName, inputAddr, inputPic.picture_buffer_size);
// 数据传输
aclRet =
aclrtMemcpy
(inputPic.picture_address, inputPic.picture_buffer_size, inputAddr,
inputPic.picture_buffer_size, ACL_MEMCPY_HOST_TO_DEVICE);
// 完成数据传输后，需及时释放内存
aclrtFreeHost
(inputAddr);
inputAddr = nullptr;
} else {
// 将输入图片读入内存中，该自定义函数ReadPicFile由用户实现
ReadPicFile(picName, inputPic.picture_address, inputPic.picture_buffer_size);
}
// 5.3 构造存放输出图片信息的结构体
// 该参数表示抠图数量
uint32_t multiCount = 1;
// cropRegionInfos数组的大小与抠图数量保持一致
hi_vpc_crop_region_info cropRegionInfos[1];
hi_vpc_pic_info outputPic;
for (uint32_t i = 0; i < multiCount; i++) {
outputPic.picture_width = 960;
outputPic.picture_height  = 540;
outputPic.picture_format = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420;
outputPic.picture_width_stride = 960;
outputPic.picture_height_stride = 540;
outputPic.picture_buffer_size = outputPic.picture_width_stride * outputPic.picture_height_stride * 3 / 2;
ret =
hi_mpi_dvpp_malloc
(0, &outputPic.picture_address, outputPic.picture_buffer_size);
// 初始化内存
if (runMode == ACL_HOST) {
aclRet =
aclrtMemset
(outputPic.picture_address, outputPic.picture_buffer_size, 0, outputPic.picture_buffer_size);
} else {
memset(outputPic.picture_address, 0, outputPic.picture_buffer_size);
}
// 表示从输入图片中抠出以左上角为原点、分辨率960*540的子图
cropRegionInfos[i].dest_pic_info = outputPic;
cropRegionInfos[i].crop_region.left_offset = 0;
cropRegionInfos[i].crop_region.top_offset = 0;
cropRegionInfos[i].crop_region.crop_width = 960;
cropRegionInfos[i].crop_region.crop_height = 540;
}
// 5.4 调用抠图接口
uint32_t taskID = 0;
ret =
hi_mpi_vpc_crop
(chnId, &inputPic, cropRegionInfos, 1, &taskID, -1);
// 5.5 等待任务处理结束，任务处理结束后，输出图片数据在outputPic.picture_address指向的内存中
uint32_t taskIDResult = taskID;
ret =
hi_mpi_vpc_get_process_result
(chnId, taskIDResult, -1);
// 5.6 如果运行模式为ACL_HOST，且Host上需要展示VPC输出的图片数据，则需要申请Host内存，通过aclrtMemcpy接口将Device的输出图片数据传输到Host；如果Host上不需要展示VPC输出的图片数据，则VPC的输出图片数据可以直接作为模型推理的输入
if (g_runMode == ACL_HOST) {
hi_vpc_pic_info outputPicHost = outputPic;
aclRet =
aclrtMallocHost
(&outputPicHost.picture_address, outputPic.picture_buffer_size);
aclRet =
aclrtMemcpy
(outputPicHost.picture_address, outputPic.picture_buffer_size, outputPic.picture_address,outputPic.picture_buffer_size, ACL_MEMCPY_DEVICE_TO_HOST);
// ......
// Host侧的数据使用完成后，释放Host内存
aclrtFreeHost
(outputPicHost.picture_address);
outputPicHost.picture_address = nullptr;
} else {
// 可以直接使用VPC的输出图片数据，在outputPic.picture_address指向的内存中
// TODO：推理相关的代码逻辑
}
// 5.7 释放输入、输出内存
ret =
hi_mpi_dvpp_free
(inputPic.picture_address);
inputPic.picture_address = nullptr;
for (uint32_t i = 0; i < multiCount; i++) {
hi_mpi_dvpp_free
(cropRegionInfos[i].dest_pic_info.picture_address);
cropRegionInfos[i].dest_pic_info.picture_address = nullptr;
}
// 6.销毁通道
ret =
hi_mpi_vpc_destroy_chn
(chnId);
// 7. 媒体数据处理系统去初始化
ret
= hi_mpi_sys_exit
();
// 8. 释放运行管理资源（依次释放Context、Device）
aclRet =
aclrtDestroyContext
(g_context);
aclRet =
aclrtResetDevice
(0);
// 9.AscendCL去初始化
aclRet =
aclFinalize
();
// ....
```
