首页 » PHP教程 » phpresizeimage技巧_若何运用CANN DVPP进行图片的等比例缩放

phpresizeimage技巧_若何运用CANN DVPP进行图片的等比例缩放

访客 2024-11-10 0

扫一扫用手机浏览

文章目录 [+]

但是事情真的是这样吗?肯定不是呀,要不我为啥会写这篇博客呢?

首先有以下几点考虑:

phpresizeimage技巧_若何运用CANN DVPP进行图片的等比例缩放

目标检测除了将图像送入模型,实际上物体在图像中的位置也被作为一部分信息参与了模型的演习,经由等比例缩放的图片在坐标转换上明显省事。
有些图像经由非等比例压缩后已经与实际场景中的物体差别很大,严重的变形会滋扰到模型的准确性。
等比例缩放更加符合我们人类的直觉。
2. 如何在昇腾AI处理器上进行等比例缩放

如果在我们的演习做事器,或者我们自己的个人打算机上,由于打算能力的相对充足,我们可以借助很多开源图像库例如opencv,pillow等进行图像的等比例缩放。
但是一旦道路端侧支配时这些开源图像库的方法每每成为全体推理过程最耗时的部分,由于他们紧张通过CPU进走运算,而在诸如200DK这样的环境上,进行这样的变换让本就不充足的CPU算力雪上加霜。
而昇腾AI处理器上拥有专门处理数字图像的硬件单元。
以是如何借助这些专用的硬件单元是十分主要的。
话不多说开始吧!

phpresizeimage技巧_若何运用CANN DVPP进行图片的等比例缩放
(图片来自网络侵删)
3. 实战讲解

紧张从以下几个方面进行讲解

步骤分解每一步在昇腾AI处理器上的实现怎么把几步组合起来完成一个完全的动作先容 AscendCL 为开拓者供应的组合API接口,只须要一个函数完成所有动作,想想都刺激<观点把稳>昇腾AI处理器在不同的环境上拥有不同运行模式,常见的分为两种,一种是类似于显卡一样插在做事器的PCIE插槽上,供应加速做事,一种是类似于200DK一样的嵌入式环境。
事情在做事器的PCIE插槽上的昇腾310内存有两种存在形态,一种便是我们常见的内存,称为host内存。
一种类似于显卡的显存,称为device内存,这种状态下须要涉及到内存拷贝的问题。
200DK这种环境,不区分host与device,只有一块device内存,以是并不涉及host与device的内存拷贝问题媒体数据处理描述图形的大小,紧张有两种表示方法,width,height,表示图像的真实宽高,widthstride,heightstride表示图像在内存中对其到128,16,2后的宽高,内存宽高标识图像在内存中的位置(但是可能有为了添补而产生的无效数据),真实宽高就标识内存中真实有效的数据。
JPEG Decode时对JPEG的源图像没有宽高的哀求,但是输出的数据,宽会对齐到128,长会对齐到16,对齐后可能会产生一部分的无效数据。
VPC 图像缩放,抠图,贴图对输入的源图像宽高对齐到2,对齐后的内存宽高为,宽对齐到16,高对齐到2,输出与输入约束条件相同JPEG Encode 对输入的源图像宽高对齐到2,对齐后的内存宽高为,宽对齐到16或者16的倍数(128时性能更好),高对齐到16。

详细的约束对齐规则

JPEG 解码

(https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/51RC2alpha007/infacldevg/aclcppdevg/aclcppdevg_03_0176.html)

专有网络

(https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/51RC2alpha007/infacldevg/aclcppdevg/aclcppdevg_03_0157.html)

JPEG 编码

(https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/51RC2alpha007/infacldevg/aclcppdevg/aclcppdevg_03_0183.html)

3.1 步骤分解

很多朋友和我一样属于一听就会,一动手就废的类型,在脑海里勾勒出万里江山,实际一上手,我是个傻子。




实际上是我们还没有节制干工作的诀窍,便是把事情分解成可以很方便用代码描述出的步骤;打算机编程让人感到头皮发麻的一个主要缘故原由便是打算机它只会一步一步来,不会跳步。
以是我们脑海里面很大略的步骤在编程实现后也可能代码很长。

1、将常见的JPEG图片解码成YUV格式的图片

aclError acldvppJpegDecodeAsync(acldvppChannelDesc channelDesc, const void data, uint32_t size, acldvppPicDesc outputDesc,aclrtStream stream)

2、由于JPEG解码有128x16的对齐哀求,以是解码后的数据边缘会涌现无效数据,须要进行裁剪

aclError acldvppVpcCropAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, aclrtStream stream)

3、进行resize的操作,没有对应的等比例缩放的接口,但是如果我们末了指定的图像大小相对付原图是等比例的,便是等比例缩放

aclError acldvppVpcResizeAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppResizeConfig resizeConfig, aclrtStream stream)

4、进行等比例缩放时须要一个,与模型大小同等的纯色背景图片作为贴图的背景,我们可以申请对应大小的一些区域然后,将对应区域全部赋值为同一个数。

aclError aclrtMemset (void devPtr, size_t maxCount, int32_t value, size_t count)

5、进行贴图

aclError acldvppVpcCropAndPasteAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, acldvppRoiConfig pasteArea, aclrtStream stream)

6、进行模型推理或编码保存

3.2 对应的变量怎么声明与初始化DVPP处理的通道的声明与销毁DVPP的一系列操作都是建立在已经声明初始化的通道上,你可以理解为初始化了干系的硬件资源

acldvppChannelDesc channelDescchannelDesc = acldvppCreateChannelDesc();aclError ret = acldvppCreateChannel(channelDesc);// 销毁与创建的顺序相反acldvppDestroyChannel(channelDesc);acldvppDestroyChannelDesc(channelDesc);各种图像的操作描述对应的图像的描述信息的创建与销毁

acldvppPicDesc picDescpicDesc = acldvppCreatePicDesc();acldvppSetPicDescData(picDesc, decodeOutDevBuffer_); // 图像的内存地址acldvppSetPicDescSize(picDesc, decodeOutBufferSize); // 图像所占的内存大小acldvppSetPicDescFormat(picDesc, PIXEL_FORMAT_YUV_SEMIPLANAR_420); // 图像的格式acldvppSetPicDescWidth(picDesc, inputWidth_); // 图像的实际宽acldvppSetPicDescHeight(picDesc, inputHeight_); // 图像的实际高acldvppSetPicDescWidthStride(picDesc, decodeOutWidthStride); // 图像在内存中对齐后的宽acldvppSetPicDescHeightStride(picDesc, decodeOutHeightStride); // 图像在内存中对齐后的高// 销毁acldvppDestroyPicDesc(picDesc);申请内存

// device侧,200DK等只有device内存的也用此接口,且只用此接口aclError aclrtMalloc(void devPtr, size_t size, aclrtMemMallocPolicy policy)aclError aclrtFree(void devPtr)// host侧aclError aclrtMallocHost(void hostPtr, size_t size)aclError aclrtFreeHost(void hostPtr)// dvpp 内存(在device侧,由于须要内存地址128对齐,以是拥有单独的接口)aclError acldvppMalloc(void devPtr, size_t size)aclError acldvppFree(void devPtr)创建抠图,贴图的区域

acldvppRoiConfig acldvppCreateRoiConfig(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom)// 销毁aclError acldvppDestroyRoiConfig(acldvppRoiConfig roiConfig)不知道图片的干系信息获取信息的接口

// 通过此接口可以方便的获取JPEG图像的宽,高,色彩通道数,编码格式,只须要传入对应的JPEG源数据地址以及大小aclError acldvppJpegGetImageInfoV2(const void data, uint32_t size, uint32_t width, uint32_t height, int32_t components, acldvppJpegFormat format)// 获取JPEG图片解码后的数据大小,由于解码后会对齐到128,以是不能任何时候都大略地依据原图大小打算解码后占用的内存大小。
aclError acldvppJpegPredictDecSize(const void data, uint32_t dataSize, acldvppPixelFormat outputPixelFormat, uint32_t decSize)// 获取PNG图像的宽和高aclError acldvppPngGetImageInfo(const void data, uint32_t dataSize, uint32_t width, uint32_t height, int32_t components)// 预测PNG图片解码后占用内存空间aclError acldvppPngPredictDecSize(const void data, uint32_t dataSize, acldvppPixelFormat outputPixelFormat, uint32_t decSize)// 获取JPEG图像编码后的占用内存空间aclError acldvppJpegPredictEncSize(const acldvppPicDesc inputDesc, const acldvppJpegeConfig config, uint32_t size)
图像缩放,贴图,抠图干系的接口(当有多个操作动作时建议利用组合接口,虽然看似是多个步骤,实际上硬件内部只是实行一次操作可以大幅度提升处理的效率)

// 缩放接口,单一接口,将缩放后的图片作为输出aclError acldvppVpcResizeAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppResizeConfig resizeConfig, aclrtStream stream)// 抠图接口,单一接口,从原图中根据ROI区域抠出一部分作为输出aclError acldvppVpcCropAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, aclrtStream stream)// 组合接口(抠图+缩放),运用处景:JPEG 解码后的图片中可能有无效的数据,直接缩放到模型须要的大小会把无效数据一起输入,可以先把真实图像的数据抠出来然后进行缩放,减少无效的数据滋扰。
aclError acldvppVpcCropResizeAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, acldvppResizeConfig resizeConfig, aclrtStream stream)// 组合接口(抠图+贴图 / 抠图+缩放+贴图)如果抠图与贴图的大小不一致会进行一步缩放操作。
aclError acldvppVpcCropAndPasteAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, acldvppRoiConfig pasteArea, aclrtStream stream)// 组合接口(抠图+贴图 / 抠图+缩放+贴图)此接口功能与上一个是同等的,但是此接口可以指定不同的缩放算法aclError acldvppVpcCropResizePasteAsync(acldvppChannelDesc channelDesc, acldvppPicDesc inputDesc, acldvppPicDesc outputDesc, acldvppRoiConfig cropArea, acldvppRoiConfig pasteArea, acldvppResizeConfig resizeConfig, aclrtStream stream)
3.3 实战讲解

效果展示:1024x688 =》 416x416

// 定义一些全局变量namespace { std::string aclConfig = "../src/acl.json"; const std::string image_path = "../data/dog2.jpg"; const std::string image_save_path = "../out/dog2.jpg"; int32_t deviceId = 0; aclrtContext context; aclrtStream stream; aclrtRunMode runMode_ = ACL_DEVICE; uint32_t model_width = 416; // 模型须要的图像宽 uint32_t model_height = 416; // 模型须要的图像高 uint32_t image_width = 0; // 图像的真实宽 uint32_t image_height = 0; // 图像的真实高 acldvppChannelDesc dvppChannelDesc = nullptr;}

开始进行解码操作

// 将图片读入内存,如果运行模式为(ACL_HOST)模式,须要申请host侧内存存放我们的原始图片数据,然后申请相同大小的device内存拷贝过去。
如果运行在(ACL_DEVICE)模式,例如200DK这样的场景,直接申请device内存,不须要进行内存的拷贝搬运。
// 经由读取图片的步骤我们该当能得到如下变量void JpegImagePtr; // 原始图像在device侧的内存地址(不管运行在那种办法下的昇腾软件栈都是如此)uint32_t JpegImageSize; // 原始图像的大小// 我们不理解图像的编码以及真实宽高,还有颜色通道数,以是须要实行获取图像信息的接口// 图像的原始宽高变量我们前面已经定义过, 以是我们现在只须要定义图像的通道数与编码办法还有图像解码后所占用的内存的大小int32_t components; // 图像的通道数acldvppJpegFormat format; // 图像的编码办法uint32_t JpegdOutSize;void JpegdOutPtr;aclError ret = acldvppJpegGetImageInfoV2(JpegImagePtr, JpegImageSize, &image_width, &height, &components, &format)aclError ret = acldvppJpegPredictDecSize(JpegImagePtr, JpegImageSize, format, &JpegdOutSize)// 申请存放解码后YUV图像的内存aclError ret = acldvppMalloc(&JpegdOutPtr, JpegdOutSize);// 设置JPEG解码后图像的描述信息(把稳JPEG解码后内存宽高对齐到128x16)acldvppPicDesc JpegdOutputPicDesc = acldvppCreatePicDesc();acldvppSetPicDescData(out_pic_desc, JpegdOutPtr);acldvppSetPicDescSize(out_pic_desc, JpegdOutSize);acldvppSetPicDescFormat(out_pic_desc, PIXEL_FORMAT_YUV_SEMIPLANAR_420);acldvppSetPicDescWidth(out_pic_desc, image_width);acldvppSetPicDescHeight(out_pic_desc, image_height);acldvppSetPicDescWidthStride(out_pic_desc, ALIGN_UP128(image_width));acldvppSetPicDescHeightStride(out_pic_desc, ALIGN_UP16(image_height));// 实行解码接口ret = acldvppJpegDecodeAsync(dvppChannelDesc, JpegImagePtr, JpegImageSize, JpegdOutputPicDesc, stream);ret = aclrtSynchronizeStream(stream_);

开始进行抠图的操作,由于我们这次须要实行抠图+缩放+贴图三个动作以是我们选择组合API进行编程

// 首先声明原图抠图的区域// 抠图的区域左偏移和上偏移都是偶数// 抠图的区域右偏移和下偏移都是奇数uint32_t left = 0;uint32_t top = 0;uint32_t right = image_width % 2 == 0 ? image_width-1 : image_width;uint32_t bottom = image_height % 2 == 0 ? image_height-1 : image_height;acldvppRoiConfig cropArea = cropConfig = acldvppCreateRoiConfig(left, right, top, bottom);// 打算等比例缩放后的新的宽高(等比例缩放后可能不知足16x2的对齐规则,须要手动进行对齐的操作)void LargeSizeAtLeast(uint32_t W, uint32_t H, uint32_t &newInputWidth, uint32_t &newInputHeight){ INFO_LOG("W:%u H:%u nw:%u, nh:%u", W, H, newInputWidth, newInputHeight); float scaleRatio = 0.0; float inputWidth = 0.0; float inputHeight = 0.0; float resizeMax = 0.0; bool maxWidthFlag = false; inputWidth = (float)W; inputHeight = (float)H; resizeMax = (float)(416); maxWidthFlag = (W >= H) ? true : false; if (maxWidthFlag == true) { newInputWidth = resizeMax; scaleRatio = resizeMax / W; // 高度2对齐 newInputHeight = scaleRatio H; newInputHeight = ALIGN_UP2(newInputHeight); INFO_LOG("scaleRatio: %.3f, modelInputWidth: %u, modelInputHeight: %d, newInputWidth: %d, newInputHeight: %d", scaleRatio, W, H, newInputWidth, newInputHeight); } else { scaleRatio = resizeMax / H; // 如果高度是长边,建议宽度在等比例缩放后再做一次16对齐。
由于vpc在输出时宽有16字节对齐约束,当贴图的宽非16对齐时,会导致在贴图的时候, // 芯片会自动进行16字节对齐,导致每次写入数据的时候都会引入部分无效数据,从而导致精度低落。
newInputWidth = scaleRatio W; newInputWidth = ALIGN_UP16(newInputWidth); newInputHeight = resizeMax; INFO_LOG("scaleRatio: %.3f, modelInputWidth: %u, modelInputHeight: %d, newInputWidth: %d, newInputHeight: %d", scaleRatio, W, H, newInputWidth, newInputHeight); }}// 设置贴图的范围左偏移哀求16对齐acldvppRoiConfig InitVpcOutConfig(uint32_t width, uint32_t height, uint32_t modelInputWidth, uint32_t modelInputHeight){ uint32_t right = 0; uint32_t bottom = 0; uint32_t left = 0; uint32_t top = 0; uint32_t left_stride; acldvppRoiConfig cropConfig; uint32_t small = width < height ? width : height; uint32_t padded_size_half; if (small == width) { padded_size_half = (modelInputWidth - width) / 2; // 贴图区域间隔左边界的间隔 left = padded_size_half; left_stride = ALIGN_UP16(left); right = (left_stride + width) % 2 == 0 ? (left_stride + width - 1) : (left_stride + width); if (left_stride + right > modelInputWidth) { while (true) { left_stride = left_stride - 16; right = (left_stride + width) % 2 == 0 ? (left_stride + width - 1) : (left_stride + width); if (left_stride + right < modelInputWidth) break; } } right = (left_stride + width) % 2 == 0 ? (left_stride + width - 1) : (left_stride + width); bottom = (modelInputHeight % 2 == 0 ? modelInputHeight - 1 : modelInputHeight); top = bottom - height + 1; } else { padded_size_half = (modelInputHeight - height) / 2; right = (modelInputWidth % 2 == 0 ? modelInputWidth - 1 : modelInputWidth); left = right + 1 - width; left_stride = ALIGN_UP16(left); top = (padded_size_half % 2 == 0 ? padded_size_half : padded_size_half + 1); bottom = (height + top - 1) % 2 == 0 ? (height + top - 2) : (height + top - 1); } INFO_LOG("left_stride=%d, right=%d, top=%d, bottom=%d\n", left_stride, right, top, bottom); cropConfig = acldvppCreateRoiConfig(left_stride, right, top, bottom); if (cropConfig == nullptr) { ERROR_LOG("acldvppCreateRoiConfig failed"); return nullptr; } return cropConfig;}// 设置末了完成等比例缩放后的图像的信息void output_ptr = nullptr;uint32_t output_size = YUV420SP_SIZE(ALIGN_UP16(model_width), ALIGN_UP2(model_height));ret = acldvppMalloc(&output_ptr, output_size); // 申请模板内存if (ret != ACL_SUCCESS) {ERROR_LOG("acl malloc output is failed");}ret = aclrtMemset(output_ptr, output_size, 128, output_size);if (ret != ACL_SUCCESS) {ERROR_LOG("mem set 128 is failed");}acldvppPicDesc output_Desc = acldvppCreatePicDesc();acldvppSetPicDescData(output_Desc, output_ptr);acldvppSetPicDescSize(output_Desc, output_size);acldvppSetPicDescFormat(output_Desc, PIXEL_FORMAT_YUV_SEMIPLANAR_420);acldvppSetPicDescWidth(output_Desc, model_width);acldvppSetPicDescHeight(output_Desc, model_height);acldvppSetPicDescWidthStride(output_Desc, ALIGN_UP16(model_width));acldvppSetPicDescHeightStride(output_Desc, ALIGN_UP2(model_height));// 实行裁剪加贴图的操作ret = acldvppVpcCropAndPasteAsync(dvppChannelDesc, JpegdOutputPicDesc, output_Desc, cropArea, pasteArea, stream);

完成了一系列的操作

总结对付DVPP处理来说,单独的接口调用须要全部知足输入和输出的长宽以及对齐的哀求但是对付 JPEG+VPC 这样的串联编程来说前一个组件的输出图像描述是后一个组件的输入描述,设定对齐内存是为了有效标定,图像在内存中的位置,如果固执的设定对齐后的值,对付后一个组件来说很有可能得到的输入是有缺失落的信息。
根据浩瀚对齐哀求我们可以看出,对付图像的原始宽高全部2对齐是一个比较好的习气,而且常用的图像规格都是偶数例如1920x1080,1280x720, 640x640, 416x416,最好不要涌现长或者宽为奇数的情形,由于硬件会自动为奇数进行对齐从而产生无效数据。
增加了处理的难度。

点击下方,第一韶光理解华为云新鲜技能~

华为云博客_大数据博客_AI博客_云打算博客_开拓者中央-华为云

标签:

相关文章

执业药师试卷代码解码药师职业发展之路

执业药师在药品质量管理、用药安全等方面发挥着越来越重要的作用。而执业药师考试,作为进入药师行业的重要门槛,其试卷代码更是成为了药师...

PHP教程 2025-02-18 阅读1 评论0

心灵代码主题曲唤醒灵魂深处的共鸣

音乐,作为一种独特的艺术形式,自古以来就承载着人类情感的表达与传递。心灵代码主题曲,以其独特的旋律和歌词,唤醒了无数人的灵魂深处,...

PHP教程 2025-02-18 阅读1 评论0

探寻福建各市车牌代码背后的文化内涵

福建省,地处我国东南沿海,拥有悠久的历史和丰富的文化底蕴。在这片充满魅力的土地上,诞生了许多具有代表性的城市,每个城市都有自己独特...

PHP教程 2025-02-18 阅读1 评论0

探寻河北唐山历史与现代交融的城市之光

河北省唐山市,一座地处渤海之滨,拥有悠久历史和独特文化的城市。这里既是古丝绸之路的起点,也是中国近代工业的发源地。如今,唐山正以崭...

PHP教程 2025-02-18 阅读1 评论0