入门昇腾AI媒体预处理技术

网友投稿 824 2022-05-30

CANN媒体数据处理

1.为什么我们要学习CANN的媒体数据处理

当然是速度快呀

媒体数据处理在不同的应用里面有不同的含义,今天我们说的媒体数据处理主要是指开发AI应用,为模型制造合适的数据,和模型推理完后后处理的一系列操作,一般包括图片的解码,编码,缩放,裁剪,视频的解码编码等等操作。

但是对深度学习了解一点的朋友可能都知道我们一般会用opencv等加速数据的处理,既然有解决办法了,再学习一种新的,有点多余吧?

NO!NO!NO!

我们都了解GPU可以加速神经网络模型的训练和推理,但是CPU也行啊,但是你了解他们之间的速度差异吗?二者效率相差可能达到几十倍甚至是上百倍,并不是CPU太逊了,也不是GPU太强了,这两种都是计算机的硬件都有着不同的特点。

入门昇腾AI媒体预处理技术

CPU的可以形象的理解为有25%的ALU(运算单元)、有25%的Control(控制单元)、50%的Cache(缓存单元)

GPU可以形象的理解为90%的ALU(运算单元),5%的Control(控制单元)、5%的Cache(缓存单元)

二者天然就有非常大的差异,然而各种数据处理运算都要有ALU完成,但是CPU为了完成一系列复杂的控制功能,运算单元的数量相比于GPU其实很少,所以面对高密度的计算任务时GPU就有天然的优势。这就是硬件优势。

然而我们的主角昇腾AI处理器是NPU,是一种相比于GPU更加适合AI运算的硬件,通过NPU的加速又可以实现数量级的效率提升。因此数据处理的操作在NPU上进行专门的加速,就可以达到数倍提升效率的成果。当需要处理的数据比较多的时候,优势就会进一步体现。

2.在专门的芯片上进行媒体数据处理这么厉害,我要怎么学习呢?

别急一步一来

第一式,摸清敌情

媒体数据处理是什么

媒体数据都可以应用在哪些方面

媒体数据处理在接口调用流程中的位置

第二式,逐个击破

在计算机视觉领域处理最多的应该也就是图片和视频了,

所以,我们先学习JPEG图片的解码和编码,

温馨提示视频中讲述的是各种数据处理的集合哦,可以按需跳跃观看

媒体数据处理

实验体验

诶嘿嘿,代码来喽!!

但是,写代码之前我们最好要知道我们整个程序的流程,那么它来喽

首先就是创建数据处理的通道,你可以把它理解为一个专为处理媒体数据的生产线,在这个生产线上会对媒体数据(图片,视频)进行加工

//1.创建和销毁图片数据处理的通道。 dvppChannelDesc_ = acldvppCreateChannelDesc(); aclError ret = acldvppCreateChannel(dvppChannelDesc_); acldvppDestroyChannel(dvppChannelDesc_);

然后要为生产线准备原材料,也就是图片或者视频的源数据(把数据加载到显存里面)

//调用aclrtGetRunMode接口获取软件栈的运行模式,如果调用aclrtGetRunMode接口获取软件栈的运行模式为ACL_HOST,则需要通过aclrtMemcpy接口将输入图片数据传输到Device,数据传输完成后,需及时释放内存;否则直接申请并使用Device的内存 aclrtRunMode runMode; ret = aclrtGetRunMode(&runMode); if(runMode == ACL_HOST){ //申请Host内存inputHostBuff,并将输入图片读入该地址,inDevBufferSize为读入图片大小 void* inputHostBuff = nullptr; inputHostBuff = malloc(inDevBufferSize); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inputHostBuff, inDevBufferSize); //申请Device内存inDevBuffer_ aclRet = acldvppMalloc(&inDevBuffer_, inDevBufferSize); //通过aclrtMemcpy接口将输入图片数据传输到Device aclRet = aclrtMemcpy(inDevBuffer_, inDevBufferSize, inputHostBuff, inDevBufferSize, ACL_MEMCPY_HOST_TO_DEVICE); } else { //申请Device输入内存inDevBuffer_ ret = acldvppMalloc(&inDevBuffer_, inBufferSize); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inDevBuffer_, inBufferSize); }

然后要为生产过程设置参数,设置图片的描述信息,这个你可以类比于我们需要告诉生产线,这批原材料的相关信息,家庭住址,高矮胖瘦,什么成分

//创建解码输出图片的描述信息,设置各属性值 //decodeOutputDesc是acldvppPicDesc类型 decodeOutputDesc_ = acldvppCreatePicDesc(); //数据描述,这个为了告诉生产线要去哪里找数据,其实就是类似于数据的首地址 acldvppSetPicDescData(decodeOutputDesc_, decodeOutDevBuffer_); //告诉生产线这张图片占用多少内存,以防一会取数据取多了 acldvppSetPicDescSize(decodeOutputDesc_, decodeOutBufferSize); //告诉生产线图片是什么格式的 acldvppSetPicDescFormat(decodeOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420); //告诉生产线图片宽多少 acldvppSetPicDescWidth(decodeOutputDesc_, inputWidth_); //告诉生产线图片高多少 acldvppSetPicDescHeight(decodeOutputDesc_, inputHeight_); //告诉生产线数据对齐后的宽是多少 acldvppSetPicDescWidthStride(decodeOutputDesc_, decodeOutWidthStride); 告诉生产线对齐后的高是多少 acldvppSetPicDescHeightStride(decodeOutputDesc_, decodeOutHeightStride);

然后准备工作完成,开始加工了

//执行异步解码,再调用aclrtSynchronizeStream接口阻塞程序运行,直到指定Stream中的所有任务都完成 ret = acldvppJpegDecodeAsync(dvppChannelDesc_, inDevBuffer_, inDevBufferSize, decodeOutputDesc_, stream_); ret = aclrtSynchronizeStream(stream_);

加工完了,该打包装箱了,贴封条了(其实就是把数据从显存拷贝回内存里面,为后面保存做准备)

if(runMode == ACL_HOST) { //该模式下,由于处理结果在Device侧,因此需要调用内存复制接口传输结果数据后,再释放Device侧内存 //申请Host内存vpcOutHostBuffer void* vpcOutHostBuffer = nullptr; vpcOutHostBuffer = malloc(decodeOutBufferSize); //通过aclrtMemcpy接口将Device的处理结果数据传输到Host aclRet = aclrtMemcpy(vpcOutHostBuffer, decodeOutBufferSize, decodeOutDevBuffer_, decodeOutBufferSize, ACL_MEMCPY_DEVICE_TO_HOST); //释放掉输入输出的device内存 (void)acldvppFree(inDevBuffer_); (void)acldvppFree(decodeOutDevBuffer_); //数据使用完成后,释放内存 free(vpcOutHostBuffer); } else { //此时运行在device侧,处理结果也在Device侧,可以根据需要操作处理结果后,释放Device侧内存 (void)acldvppFree(inDevBuffer_); (void)acldvppFree(decodeOutDevBuffer_); }

解码结束后,释放资源,包括解码输出图片的描述信息、解码输出内存、通道描述信息、通道等(干完活你得把场地收拾好)

acldvppDestroyPicDesc(decodeOutputDesc_); acldvppDestroyChannel(dvppChannelDesc_); (void)acldvppDestroyChannelDesc(dvppChannelDesc_);

搞定了

解码完成了,我们想对图片进行一个缩放,该咋办呢??

同样先来一个流程图

其实还是那个套路

先准备生产线

// 创建图片数据处理通道时的通道描述信息,dvppChannelDesc_是acldvppChannelDesc类型 dvppChannelDesc_ = acldvppCreateChannelDesc();

创建图片缩放配置数据、指定缩放算法,图片缩放后会成什么鸟样子和采用什么算法有很大差异,中国厨子和外国厨子做出的菜能一个味吗??

//resizeConfig_是acldvppResizeConfig类型 acldvppResizeConfig *resizeConfig_ = acldvppCreateResizeConfig(); aclError ret = acldvppSetResizeConfigInterpolation(resizeConfig_, 0);

准备原材料(将数据读到内存,再拷贝到显存)

//申请输入内存(区分运行状态) //调用aclrtGetRunMode接口获取软件栈的运行模式,如果调用aclrtGetRunMode接口获取软件栈的运行模式为ACL_HOST,则需要通过aclrtMemcpy接口将输入图片数据传输到Device,数据传输完成后,需及时释放内存;否则直接申请并使用Device的内存 aclrtRunMode runMode; ret = aclrtGetRunMode(&runMode); //inputPicWidth、inputPicHeight分别表示图片的对齐后宽、对齐后高,此处以YUV420SP格式的图片为例 uint32_t resizeInBufferSize = inputPicWidth * inputPicHeight * 3 / 2; if(runMode == ACL_HOST) { //申请Host内存vpcInHostBuffer void* vpcInHostBuffer = nullptr; vpcInHostBuffer = malloc(resizeInBufferSize); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, vpcInHostBuffer, resizeInBufferSize); //申请Device内存resizeInDevBuffer_ aclRet = acldvppMalloc(&resizeInDevBuffer_, resizeInBufferSize); //通过aclrtMemcpy接口将输入图片数据传输到Device aclRet = aclrtMemcpy(resizeInDevBuffer_, resizeInBufferSize, vpcInHostBuffer, resizeInBufferSize, ACL_MEMCPY_HOST_TO_DEVICE); //数据传输完成后,及时释放内存 free(vpcInHostBuffer); } else { //申请Device输入内存resizeInDevBuffer_ ret = acldvppMalloc(&resizeInDevBuffer_, resizeInBufferSize); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, resizeInDevBuffer_, resizeInBufferSize); }

这里有个点要注意,图片缩放以后占用的空间必然会发生变化,因此我们需要一个新的容器才能存下,正所谓鲲之大,一锅炖不下

//申请缩放输出内存resizeOutBufferDev_,内存大小resizeOutBufferSize_根据计算公式得出 //outputPicWidth、outputPicHeight分别表示图片的对齐后宽、对齐后高,此处以YUV420SP格式的图片为例 uint32_t resizeOutBufferSize_ = outputPicWidth * outputPicHeight * 3 / 2; ret = acldvppMalloc(&resizeOutBufferDev_, resizeOutBufferSize_)

然后要告诉生产线,做之前材料是啥样子,完成后要是什么样子

//创建缩放输入图片的描述信息,并设置各属性值 //resizeInputDesc_是acldvppPicDesc类型 resizeInputDesc_ = acldvppCreatePicDesc(); //告诉昇腾处理器去哪个货架找数据 acldvppSetPicDescData(resizeInputDesc_, resizeInDevBuffer_); //拿多少数据 acldvppSetPicDescSize(resizeInputDesc_, resizeInBufferSize); //输入的图片是哪片山头的 acldvppSetPicDescFormat(resizeInputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420); //输入图片宽度 acldvppSetPicDescWidth(resizeInputDesc_, inputWidth_); //输入图片的高度 acldvppSetPicDescHeight(resizeInputDesc_, inputHeight_); //对齐后的宽度 acldvppSetPicDescWidthStride(resizeInputDesc_, inputWidthStride); //对齐后的高度 acldvppSetPicDescHeightStride(resizeInputDesc_, inputHeightStride); //创建缩放输出图片的描述信息,并设置各属性值 //如果缩放的输出图片作为模型推理的输入,则输出图片的宽高要与模型要求的宽高保持一致 //resizeOutputDesc_是acldvppPicDesc类型 resizeOutputDesc_ = acldvppCreatePicDesc(); //处理完的数据放哪里,包装箱放哪里 acldvppSetPicDescData(resizeOutputDesc_, resizeOutBufferDev_); //这个产品多大,占多大地方 acldvppSetPicDescSize(resizeOutputDesc_, resizeOutBufferSize_); //哪个山头的 acldvppSetPicDescFormat(resizeOutputDesc_, PIXEL_FORMAT_YUV_SEMIPLANAR_420); //想要缩放到的宽度 acldvppSetPicDescWidth(resizeOutputDesc_, resizeOutputWidth_); //想要缩放的高度 acldvppSetPicDescHeight(resizeOutputDesc_, resizeOutputHeight_); //对齐后的宽度 acldvppSetPicDescWidthStride(resizeOutputDesc_, resizeOutputWidthStride); //对齐后的高度 acldvppSetPicDescHeightStride(resizeOutputDesc_, resizeOutputHeightStride);

开始缩放吧

//执行异步缩放,再调用aclrtSynchronizeStream接口阻塞程序运行,直到指定Stream中的所有任务都完成 ret = acldvppVpcResizeAsync(dvppChannelDesc_, resizeInputDesc_, resizeOutputDesc_, resizeConfig_, stream_); ret = aclrtSynchronizeStream(stream_);

缩放结束后,释放资源,包括缩放输入/输出图片的描述信息、缩放输入/输出内存

acldvppDestroyPicDesc(resizeInputDesc_); acldvppDestroyPicDesc(resizeOutputDesc_); if(runMode == ACL_HOST) { //该模式下,由于处理结果在Device侧,因此需要调用内存复制接口传输结果数据后,再释放Device侧内存 //申请Host内存vpcOutHostBuffer void* vpcOutHostBuffer = nullptr; vpcOutHostBuffer = malloc(resizeOutBufferSize_); //通过aclrtMemcpy接口将Device的处理结果数据传输到Host aclRet = aclrtMemcpy(vpcOutHostBuffer, resizeOutBufferSize_, resizeOutBufferDev_, resizeOutBufferSize_, ACL_MEMCPY_DEVICE_TO_HOST); //释放掉输入输出的device内存 (void)acldvppFree(resizeInDevBuffer_); (void)acldvppFree(resizeOutBufferDev_); //数据使用完成后,释放内存 free(vpcOutHostBuffer); } else { //此时运行在device侧,处理结果也在Device侧,可以根据需要操作处理结果后,释放Device侧内存 (void)acldvppFree(resizeInDevBuffer_); (void)acldvppFree(resizeOutBufferDev_); }

嘿嘿,又完成了!

最后一个,解码视频流

流程图给爷上

这个视频解码与图片有点不一样,我们都了解视频就是一堆图像连续播放.一秒钟可能有几十帧的图像,所以我们就不可以像,一条流水线一样从前到后完全做完在做下一个,要把解码完成的后处理动作转移到另一条生产线上

创建回调函数,处理解码视频以外的所有事情

//3.创建回调函数,这个函数就是另一条生产线上做的事情,除了不负责解码,啥都做 void callback(acldvppStreamDesc *input, acldvppPicDesc *output, void *userdata) { static int count = 1; if (output != nullptr) { //获取VDEC解码的输出内存,调用自定义函数WriteToFile将输出内存中的数据写入文件后,再调用acldvppFree接口释放输出内存 void *vdecOutBufferDev = acldvppGetPicDescData(output); if (vdecOutBufferDev != nullptr) { // 0: vdec success; others, vdec failed //retCode为0表示解码成功,为1表示解码失败。如果解码失败,需要根据日志中的返回码判断具体的问题,返回码请参见返回码说明。 int retCode = acldvppGetPicDescRetCode(output); if (retCode == 0) { // process task: write file uint32_t size = acldvppGetPicDescSize(output); std::string fileNameSave = "outdir/image" + std::to_string(count); // vdec输出结果在device侧,在WriteToFile方法中进行下述处理 if (!Utils::WriteToFile(fileNameSave.c_str(), vdecOutBufferDev, size)) { ERROR_LOG("write file failed."); } } else { ERROR_LOG("vdec decode frame failed."); } // free output vdecOutBufferDev aclError ret = acldvppFree(vdecOutBufferDev); } // 释放acldvppPicDesc类型的数据,表示解码后输出图片描述数据 aclError ret = acldvppDestroyPicDesc(output); } // free input vdecInBufferDev and destroy stream desc if (input != nullptr) { void *vdecInBufferDev = acldvppGetStreamDescData(input); if (vdecInBufferDev != nullptr) { aclError ret = acldvppFree(vdecInBufferDev); } // 释放acldvppStreamDesc类型的数据,表示解码的输入码流描述数据 aclError ret = acldvppDestroyStreamDesc(input); } INFO_LOG("success to callback %d.", count); count++; }

准备数据,把数据的准备好,各项信息都标好

//4.创建视频码流处理通道时的通道描述信息,设置视频处理通道描述信息的属性,其中线程、callback回调函数需要用户提前创建。 //vdecChannelDesc_是aclvdecChannelDesc类型 vdecChannelDesc_ = aclvdecCreateChannelDesc(); //创建一个标致这条生产线的标志 ret = aclvdecSetChannelDescChannelId(vdecChannelDesc_, 10); //另一条生产线的位置 ret = aclvdecSetChannelDescThreadId(vdecChannelDesc_, threadId_); //另一条生产线要干的工作 ret = aclvdecSetChannelDescCallback(vdecChannelDesc_, callback); //示例中使用的是H265_MAIN_LEVEL视频编码协议,视频的编码 ret = aclvdecSetChannelDescEnType(vdecChannelDesc_, static_cast(enType_)); //示例中使用的是PIXEL_FORMAT_YVU_SEMIPLANAR_420,解码后的图片的编码 ret = aclvdecSetChannelDescOutPicFormat(vdecChannelDesc_, static_cast(format_)); //5.创建视频码流处理的通道 ret = aclvdecCreateChannel(vdecChannelDesc_);

//调用aclrtGetRunMode接口获取软件栈的运行模式,如果调用aclrtGetRunMode接口获取软件栈的运行模式为ACL_HOST,则需要通过aclrtMemcpy接口将输入图片数据传输到Device,数据传输完成后,需及时释放内存;否则直接申请并使用Device的内存 aclrtRunMode runMode; ret = aclrtGetRunMode(&runMode); if(runMode == ACL_HOST){ //申请Host内存inputHostBuff void* inputHostBuff= nullptr; //inBufferSize_为输入码流大小 inputHostBuff= malloc(inBufferSize_); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inputHostBuff, inBufferSize_); //申请Device内存inBufferDev_ aclRet = acldvppMalloc(&inBufferDev_, inBufferSize_); //通过aclrtMemcpy接口将输入图片数据传输到Device aclRet = aclrtMemcpy(inBufferDev_, inBufferSize_, inputHostBuff, inBufferSize_, ACL_MEMCPY_HOST_TO_DEVICE); //数据传输完成后,及时释放内存 free(inputHostBuff); } else { //申请Device输入内存dataDev, StreamBufferSize为输入码流大小 ret = acldvppMalloc(&inBufferDev_, inBufferSize_); //将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inBufferDev_, inBufferSize_); }

标志出来输入的视频的个人信息,以及输出的图片的个人信息

//7.1 创建输入视频码流描述信息,设置码流信息的属性 streamInputDesc_ = acldvppCreateStreamDesc(); //inBufferDev_表示Device存放输入视频数据的内存,inBufferSize_表示内存大小 ret = acldvppSetStreamDescData(streamInputDesc_, inBufferDev_); ret = acldvppSetStreamDescSize(streamInputDesc_, inBufferSize_); //7.2 申请Device内存picOutBufferDev_,用于存放VDEC解码后的输出数据 ret = acldvppMalloc(&picOutBufferDev_, size); //7.3 创建输出图片描述信息,设置图片描述信息的属性 //picOutputDesc_是acldvppPicDesc类型 picOutputDesc_ = acldvppCreatePicDesc(); ret = acldvppSetPicDescData(picOutputDesc_, picOutBufferDev_); ret = acldvppSetPicDescSize(picOutputDesc_, size); ret = acldvppSetPicDescFormat(picOutputDesc_, static_cast(format_));

开始解码

//7.4 执行视频码流解码,解码每帧数据后,系统自动调用callback回调函数将解码后的数据写入文件,再及时释放相关资源 ret = aclvdecSendFrame(vdecChannelDesc_, streamInputDesc_, picOutputDesc_, nullptr, nullptr);

清理一下场地

//8.释放图片处理通道、图片描述信息 ret = aclvdecDestroyChannel(vdecChannelDesc_); aclvdecDestroyChannelDesc(vdecChannelDesc_);

至此,图片解码,缩放,视频解码已经全部完成

本文的介绍,还有好多函数的细节未曾解释清楚,欢迎各位查看官方的文档,进行系统化的学习,让我们一起建设国产软件的生态

昇腾文档

云端实践 媒体处理 昇腾 机器学习

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:RPA进阶(一)RPA工具Uipath入门基础知识
下一篇:走进RPA世界
相关文章