楼主: Snow7273
14 0

CANN Samples(十一):媒体处理接口V1与V2的取舍与迁移 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-11-28
最后登录
2018-11-28

楼主
Snow7273 发表于 2025-12-5 21:02:18 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

在CANN(Compute Architecture for Neural Networks)的实际开发中,图像与视频的预处理是不可或缺的核心步骤。无论是执行图像分类、目标检测,还是其他计算机视觉任务,都需要对输入的图片或视频流进行解码、缩放、裁剪等操作。为了在昇腾(Ascend)AI芯片上高效实现这些功能,CANN提供了专用的硬件加速模块——DVPP(Digital Vision Pre-Processing),用以提升处理性能并降低系统开销。

DVPP的接口并非静态不变,而是经历了从V1版本到V2版本的重大演进。早期的V1接口

acldvpp
逐渐被更现代化的V2接口所取代
himpi
,后者通常通过AclLite库进行封装调用。这两个版本在设计理念、编程范式以及开发者体验方面存在显著差异。掌握两者之间的区别,并能根据项目需求合理选择或完成版本迁移,已成为CANN开发者的一项关键能力。

本文将深入剖析DVPP的V1与V2接口,结合具体代码示例,帮助你理解各自的架构特点,并提供一份实用的升级迁移指导。

1. V1接口:细粒度的手动管理模式
acldvpp

V1接口主要指以 acldvpp 开头的一系列C语言风格API。其设计哲学偏向“过程式编程”,将每一个DVPP操作拆分为多个独立函数调用,赋予开发者极高的控制自由度。这种模式类似于驾驶手动挡汽车——油门、离合、换挡均需手动协调,灵活性强但复杂度高。

下面我们通过一个典型的“裁剪并拼接”(Crop and Paste)操作来展示V1接口的具体使用流程,该示例源自项目:

cplusplus/level2_simple_inference/0_data_process/smallResolution_cropandpaste/src/dvpp_process.cpp

1.1 初始化:创建DVPP通道

所有基于DVPP的操作都必须在一个“通道”(Channel)上下文中运行。因此,第一步是创建通道描述符,并据此初始化实际的处理通道。

// 源文件路径:cplusplus/level2_simple_inference/0_data_process/smallResolution_cropandpaste/src/dvpp_process.cpp
Result DvppProcess::InitResource()
{
    // 1. 创建通道描述符
    dvppChannelDesc_ = acldvppCreateChannelDesc();
    if (dvppChannelDesc_ == nullptr) {
        ERROR_LOG("acldvppCreateChannelDesc failed");
        return FAILED;
    }

    // 2. 使用描述符创建DVPP通道
    aclError aclRet = acldvppCreateChannel(dvppChannelDesc_);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("acldvppCreateChannel failed, errorCode = %d", static_cast<int32_t>(aclRet));
        return FAILED;
    }

    INFO_LOG("dvpp init resource success");
    return SUCCESS;
}

acldvppChannelDesc
:这是一个用于定义DVPP通道属性的数据结构,相当于一份配置清单,指示系统如何构建所需的处理环境。

acldvppCreateChannel
:此函数依据上述配置,在昇腾设备上实际建立一个可用的DVPP处理通道。

1.2 繁琐但必要:配置输入输出描述信息

接下来需要明确指定输入数据和预期输出的格式细节,包括图像编码类型、分辨率、内存地址、步长(Stride)等参数。每一项配置都依赖特定的

acldvppSetPicDesc*
系列函数逐一设置。

// 源文件路径:cplusplus/level2_simple_inference/0_data_process/smallResolution_cropandpaste/src/dvpp_process.cpp
Result DvppProcess::InitCropAndPasteOutputDesc()
{
    // 计算输出图像的对齐后宽高
    uint32_t vpcOutWidthStride = AlignSize(outWidth_, 16); // 宽度按16字节对齐
    uint32_t vpcOutHeightStride = AlignSize(outHeight_, 2);  // 高度按2字节对齐

    // 根据YUV420SP格式计算所需输出缓冲区大小
    vpcOutBufferSize_ = vpcOutWidthStride * vpcOutHeightStride * 3 / 2;

    // 关键步骤:为DVPP操作申请专用的设备内存

aclError aclRet = acldvppMalloc(&vpcOutBufferDev_, vpcOutBufferSize_);
if (aclRet != ACL_SUCCESS) {
    ERROR_LOG("malloc output image buffer failed, errorCode = %d", static_cast<int32_t>(aclRet));
    return FAILED;
}

// 创建输出图片的描述符
vpcOutputDesc_ = acldvppCreatePicDesc();
if (vpcOutputDesc_ == nullptr) {
    ERROR_LOG("acldvppCreatePicDesc vpcOutputDesc_ failed");
    return FAILED;
}

// 配置图片描述信息,包括内存地址、格式、尺寸等关键参数
(void)acldvppSetPicDescData(vpcOutputDesc_, vpcOutBufferDev_);
(void)acldvppSetPicDescFormat(vpcOutputDesc_, outFormat_);
(void)acldvppSetPicDescWidth(vpcOutputDesc_, outWidth_);
(void)acldvppSetPicDescHeight(vpcOutputDesc_, outHeight_);
(void)acldvppSetPicDescWidthStride(vpcOutputDesc_, vpcOutWidthStride);
(void)acldvppSetPicDescHeightStride(vpcOutputDesc_, vpcOutHeightStride);
(void)acldvppSetPicDescSize(vpcOutputDesc_, vpcOutBufferSize_);

return SUCCESS;
acldvppMalloc
: 该函数是V1接口中尤为关键的一个环节。由于DVPP模块要求使用物理上连续的专用内存,而常规方式申请的Device内存并不满足此条件,因此无法直接用于图像处理流程。
aclrtMalloc
普通的内存分配方法不能支持DVPP操作,必须通过特定接口进行内存获取。
acldvppMalloc
正是为此类场景设计,专门用于申请符合DVPP要求的物理连续内存块。
acldvppPicDesc
: 图像描述符是整个处理过程中的核心结构之一,可视为图像数据的“元数据名片”。它包含了图像在内存中的位置、格式、宽高、步长和总大小等全部关键属性信息。无论是作为输入还是输出,每张图像都必须配备这样一个描述符以供系统识别和调度。

1.3 任务提交与资源释放

完成所有配置后,即可调用具体的DVPP异步处理函数(例如:
acldvppVpcCropAndPasteAsync
)来启动图像裁剪与拼接等操作。任务执行完毕后,需主动清理所创建的各类资源,包括描述符、通道句柄以及已分配的DVPP内存。

// 异步提交图像处理任务
aclError aclRet = acldvppVpcCropAndPasteAsync(dvppChannelDesc_, vpcInputDesc_,
    vpcOutputDesc_, cropAndPasteRoiCfg_, cropAndPasteCfg_, stream_);

// ... 待任务完成后的资源回收步骤 ...

// 销毁输入输出及配置相关的描述符
acldvppDestroyPicDesc(vpcInputDesc_);
acldvppDestroyPicDesc(vpcOutputDesc_);
acldvppDestroyRoiConfig(cropAndPasteRoiCfg_);
acldvppDestroyCropAndPasteConfig(cropAndPasteCfg_);

// 关闭并销毁DVPP通道及相关描述符
if (dvppChannelDesc_ != nullptr) {
    aclRet = acldvppDestroyChannel(dvppChannelDesc_);
    // ...
    acldvppDestroyChannelDesc(dvppChannelDesc_);
    dvppChannelDesc_ = nullptr;
}

// 释放之前分配的输出缓冲区内存
if (vpcOutBufferDev_ != nullptr) {
    (void)acldvppFree(vpcOutBufferDev_);
    vpcOutBufferDev_ = nullptr;
}

V1接口总结

优点:提供极高的控制精度,允许开发者对每一个处理细节进行定制化设置,适用于需要深度优化和精细调控的底层开发场景。

缺点:API使用复杂,代码冗长;需要手动管理通道、内存、描述符等多种资源;涉及内存对齐、物理连续性、格式匹配等专业知识,学习成本高,容易出错,整体开发效率偏低。

2. V2接口(基于AclLite封装):自动化管理模式

V2接口,即
himpi
,在架构设计上更为现代化和用户友好。尽管底层仍依赖于相同的驱动能力,但我们通常不会直接调用原始的底层API。
AclLite

通过官方提供的库进行间接调用,而非直接操作底层接口。AclLite对V2接口进行了面向对象的封装,显著简化了开发流程,使开发者能够更加聚焦于核心业务逻辑的实现。这种设计类似于自动化资源管理机制,底层资源的分配与释放均由系统自动完成。 在
inference/modelInference/sampleResnetDVPP/cppACLLite/src/sampleResnetDVPP.cpp
环境中,借助AclLite封装后的V2接口可高效完成图像预处理任务。以下将详细介绍其实现方式。

2.1 核心从一个类实例开始

在AclLite框架中,所有DVPP相关操作均围绕一个关键类展开。开发者无需手动创建和维护通道资源,仅需实例化该类的对象即可启动整个处理流程。
// 源码路径:inference/modelInference/sampleResnetDVPP/cppACLLite/src/sampleResnetDVPP.cpp
class SampleResnetDVPP {
    // ...
private:
    AclLiteImageProc imageProcess_; // 图像处理核心对象
    // ...
};
AclLiteImageProc

上述代码中的
imageProcess_
对象,在其构造函数与析构函数中会自动完成DVPP通道的初始化与销毁工作。这一设计遵循C++中的RAII(Resource Acquisition Is Initialization)原则,确保资源使用的安全性,有效防止内存泄漏或句柄未释放等问题。

2.2 功能调用的高度封装

一旦拥有了
imageProcess_
实例,诸如图像解码、尺寸缩放等操作便转化为简单的成员函数调用,完全屏蔽了诸如
PicDesc
acldvppMalloc
等底层实现细节。
// 源码路径:inference/modelInference/sampleResnetDVPP/cppACLLite/src/sampleResnetDVPP.cpp
Result SampleResnetDVPP::ProcessInput(const string testImgPath)
{
    // 1. 将JPG图像数据读入内存
    ImageData image;
    AclLiteError ret = ReadJpeg(image, testImgPath);
    // ...

    // 2. 将图像数据从Host端拷贝至Device端(DVPP专用内存)
    // AclLite内部已封装 acldvppMalloc 的调用逻辑
    ImageData imageDevice;
    ret = CopyImageToDevice(imageDevice, image, runMode_, MEMORY_DVPP);
    // ...

    // 3. 解码:将JPEG格式转换为YUV格式
    ImageData yuvImage;
    ret = imageProcess_.JpegD(yuvImage, imageDevice);
    if (ret != ACL_SUCCESS) {
        ACLLITE_LOG_ERROR("Convert jpeg to yuv failed, errorCode is %d", ret);
        return FAILED;
    }

    // 4. 缩放:调整YUV图像至模型所需输入尺寸
    ret = imageProcess_.Resize(resizedImage_, yuvImage, modelWidth_, modelHeight_);
    if (ret != ACL_SUCCESS) {
        ACLLITE_LOG_ERROR("Resize image failed, errorCode is %d", ret);
        return FAILED;
    }
    return SUCCESS;
}
CopyImageToDevice

该函数是AclLite提供的一种便捷工具方法。用户只需指定目标设备类型为
MEMORY_DVPP
,系统便会自动调用
acldvppMalloc
完成设备内存的申请及数据传输,彻底隐藏底层复杂性。
imageProcess_.JpegD()
imageProcess_.Resize()

这正是V2接口的核心优势所在。原本复杂的解码与缩放流程,现在仅需两行函数调用即可完成。AclLite在内部自动完成了输入输出描述符的创建、参数配置、异步任务提交与同步等待、临时资源回收等一系列在V1接口中必须手动处理的操作。

V2接口(AclLite封装)总结

  • 优点:API高度封装,采用面向对象设计,代码简洁清晰,开发效率高,出错概率低,利于快速构建应用。
  • 缺点:控制粒度相对较粗,对于需要深度定制或非标准处理流程的高级场景,灵活性不及V1接口。

3. V1与V2接口核心差异对比及迁移建议

通过以上分析,可以明确V1接口与基于AclLite封装的V2接口之间的主要区别:
特性 V1 (
acldvpp
)
V2 (AclLite 封装)
API风格 过程式 C 风格 API 面向对象 C++ 风格 API
资源管理 需手动创建与销毁(如通道、描述符) 自动管理(基于RAII机制)
内存管理 需显式调用
acldvppMalloc
隐式处理(例如
CopyImageToDevice
代码复杂度 较高,代码冗长 较低,结构简洁
易用性 较低,学习成本高 高,接口直观易懂
灵活性 极高,可精细控制每个执行步骤 较高,但部分底层细节被封装
himpi

3.1 如何选择与迁移?

在实际开发过程中,面对DVPP的V1与V2接口,如何做出合理的技术选型?迁移又需要付出怎样的成本?以下是针对不同项目阶段的建议。

新项目推荐方案:
对于新建项目,若无特殊底层功能需求,优先选用基于AclLite封装的V2接口。该方式能显著降低开发复杂度,减少人为错误,并增强代码的可读性与后期维护性。仅当项目必须调用特定DVPP底层能力时,才考虑使用更底层的V1接口。

旧项目维护策略:
若现有系统已稳定运行于V1接口之上,则无需强制升级或重构。尽管V1接口结构复杂,但仍在CANN框架支持范围内。是否启动迁移应基于实际需求判断——例如需引入新的图像或视频预处理模块,或当前代码因V1的繁琐流程导致维护困难时,方可启动向V2的演进。

迁移实施步骤

若决定从V1迁移到AclLite封装的V2接口,可参考以下流程进行平滑过渡:

第一步:集成AclLite组件
在工程中引入AclLite相关的头文件,并完成对应库文件的链接配置,为后续重构提供基础支持。

第二步:统一资源管理机制
移除原有用于资源初始化和释放的低级调用代码,如:

acldvppCreateChannel
acldvppDestroyChannel
等。取而代之的是通过创建
AclLiteImageProc
对象实例来自动托管资源生命周期。

第三步:调整功能函数调用方式
定位项目中直接调用

acldvppVpc*Async
acldvppJpeg*Async
等底层函数的位置,将其替换为
AclLiteImageProc
对象所提供的成员方法,例如:
Resize
JpegD
Crop
等,实现更高层次的抽象封装。

第四步:优化内存操作逻辑
将原本分散的手动内存管理操作,如组合使用

acldvppMalloc
aclrtMemcpy
的方式,改为调用AclLite提供的高级接口,如
CopyImageToDevice
CopyImageToHost
等。同时,清除所有显式调用
acldvppFree
的相关代码,交由框架内部统一处理。

第五步:删除描述符相关代码
彻底移除项目中关于

acldvppCreatePicDesc
acldvppSetPicDesc*
以及
acldvppDestroyPicDesc
等描述符的定义与操作代码。这些细节已被AclLite内部封装,外部无需再干预。

4. 总结

DVPP的V1与V2接口体现了两种不同的架构理念。V1

acldvpp
接口赋予开发者高度的控制自由,适用于对性能与流程有精细要求的场景,但随之而来的是陡峭的学习曲线和较高的出错风险。

相比之下,基于AclLite的V2接口通过合理的抽象,在可控范围内牺牲部分灵活性,换取了更高的开发效率与更强的代码稳定性。

对于绝大多数应用层开发者而言, 采用AclLite的V2接口无疑是更加简洁且高效的路径 。它屏蔽了大量底层硬件交互的复杂性,使开发人员能够专注于核心业务逻辑与算法优化。提升硬件利用率的同时简化软件开发体验,正是现代计算平台持续演进的方向所在。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:samples Sample AMPL PLE MPL

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-9 17:34