HarmonyOS NEXT实战:“相机分段式拍照”性能提升实践

2024-08-31 02:20

本文主要是介绍HarmonyOS NEXT实战:“相机分段式拍照”性能提升实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

相机拍照性能依赖算法处理的速度,而处理效果依赖算法的复杂度,算法复杂度越高的情况下会导致处理时间就越长。目前系统相机开发有两种相机拍照方案,分别是相机分段式拍照和相机单段式拍照:

  • 分段式拍照是系统相机开发的重要功能之一,即相机拍照可输出低质量图用作缩略图,提升用户感知拍照速度,同时使用高质量图保证最后的成图质量达到系统相机的水平,构筑相机性能竞争力。这样可以优化系统的拍照响应时延,从而提升用户的体验。
  • 单段式拍照是在拍照过程中通过多帧融合以及多个底层算法仅会返回一张高质量图片,这样导致Shot2See(Shot2See指的是从用户点击拍照控件到在缩略图显示区域显示缩略图)完成时延比较长。

分段式拍照和单段式拍照返回的图片在全质量图的情况下图片质量是一致的,但是在低质量的情况下单段式拍照的图片质量要优于分段式拍照。如果开发者考虑Shot2See的完成时延以及获取全质量图,建议使用分段式拍照,否则的话,建议使用单段式拍照。 本篇文章主要以相机Shot2See场景为例,来展示分段式拍照Shot2See的完成时延要低于单段式拍照。

分段式拍照流程示意图

camera-subsection-mode-image

效果展示

如下效果图所示,单段式拍照从点击拍照控件到在缩略图显示区域显示缩略图的耗时比分段式拍照的时间长。

单段式拍照效果图分段式拍照效果图

camera-single-stage-mode-video

camera-subsection-mode-video

性能对比分析方式

代码静态校验:在相机类应用中,如果使用单段式拍照,拍照过程中该场景下仅会返回一张图片,将图片用作Shot2See后的缩略图则会导致Shot2See完成时延比较长。

动态校验:开发者可以通过DevEco Studio中的Profiler工具去抓取Trace,获取到Trace之后,根据PhotoOutputNapi::Capture和OnBufferAvailable找到对应的Trace Marker,通过两者之间的时间段来分析耗时,通过Trace可以查看,单段式拍照的时长超过1s,而分段式拍照的时长为743.1ms。

单段式拍照性能数据如下图所示:

分段式拍照耗时数据如下图所示:

性能对比分析表:

拍照实现方式耗时(局限不同设备和场景,数据仅供参考)
单段式拍照2.1s
分段式拍照741.3ms

优化思路:在需要加快Shot2See完成时延的场景下,使用相机框架开发的分段式拍照方案,加快第一段照片生成的速度。

场景示例

下面以应用中相机Shot2See场景为例,通过单段式拍照和分段式拍照的性能功耗对比,来展示两者的性能差异。

单段式拍照:

单段式拍照使用了on(type:'photoAvailable',callback:AsyncCallback):void接口注册了全质量图的监听,默认不使能分段式拍照。具体操作步骤如下所示:

  1. 相机媒体数据写入XComponent组件中,用来显示图像效果。具体代码如下所示:

    XComponent({id: 'componentId',type: 'surface',controller: this.mXComponentController
    }).onLoad(async () => {Logger.info(TAG, 'onLoad is called');this.surfaceId = this.mXComponentController.getXComponentSurfaceId();GlobalContext.get().setObject('cameraDeviceIndex', this.defaultCameraDeviceIndex);GlobalContext.get().setObject('xComponentSurfaceId', this.surfaceId);let surfaceRect: SurfaceRect = {surfaceWidth: Constants.X_COMPONENT_SURFACE_HEIGHT, surfaceHeight: Constants.X_COMPONENT_SURFACE_WIDTH};this.mXComponentController.setXComponentSurfaceRect(surfaceRect);Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);await CameraService.initCamera(this.surfaceId, this.defaultCameraDeviceIndex);})
  2. initCamera函数完成一个相机生命周期初始化的过程。

  • 首先通过getCameraManager来获取CameraMananger相机管理器类。

  • 调用getSupportedCameras和getSupportedOutputCapability方法来获取支持的camera设备以及设备能力集。

  • 调用createPreviewOutput和createPhotoOutput方法来创建预览输出和拍照输出对象。

  • 使用CameraInput的open方法来打开相机输入,通过onCameraStatusChange函数来创建CameraManager注册回调。

  • 最后调用sessionFlowFn函数创建并开启Session。具体代码如下所示:

    async initCamera(surfaceId: string, cameraDeviceIndex: number): Promise<void> {Logger.info(TAG, `initCamera cameraDeviceIndex: ${cameraDeviceIndex}`);this.photoMode = AppStorage.get('photoMode')if (!this.photoMode) {return;}try {await this.releaseCamera();// 获取相机管理器实例this.cameraManager = this.getCameraManagerFn();if (this.cameraManager === undefined) {Logger.error(TAG, 'cameraManager is undefined');return;}// 获取支持指定的相机设备对象this.cameras = this.getSupportedCamerasFn(this.cameraManager);if (this.cameras.length < 1 || this.cameras.length < cameraDeviceIndex + 1) {return;}this.curCameraDevice = this.cameras[cameraDeviceIndex];let isSupported = this.isSupportedSceneMode(this.cameraManager, this.curCameraDevice);if (!isSupported) {Logger.error(TAG, 'The current scene mode is not supported.');return;}let cameraOutputCapability =this.cameraManager.getSupportedOutputCapability(this.curCameraDevice, this.curSceneMode);let previewProfile = this.getPreviewProfile(cameraOutputCapability);if (previewProfile === undefined) {Logger.error(TAG, 'The resolution of the current preview stream is not supported.');return;}this.previewProfileObj = previewProfile;// 创建previewOutput输出对象this.previewOutput = this.createPreviewOutputFn(this.cameraManager, this.previewProfileObj, surfaceId);if (this.previewOutput === undefined) {Logger.error(TAG, 'Failed to create the preview stream.');return;}// 监听预览事件this.previewOutputCallBack(this.previewOutput);let photoProfile = this.getPhotoProfile(cameraOutputCapability);if (photoProfile === undefined) {Logger.error(TAG, 'The resolution of the current photo stream is not supported.');return;}this.photoProfileObj = photoProfile;// 创建photoOutPut输出对象this.photoOutput = this.createPhotoOutputFn(this.cameraManager, this.photoProfileObj);if (this.photoOutput === undefined) {Logger.error(TAG, 'Failed to create the photo stream.');return;}// 创建cameraInput输出对象this.cameraInput = this.createCameraInputFn(this.cameraManager, this.curCameraDevice);if (this.cameraInput === undefined) {Logger.error(TAG, 'Failed to create the camera input.');return;}// 打开相机let isOpenSuccess = await this.cameraInputOpenFn(this.cameraInput);if (!isOpenSuccess) {Logger.error(TAG, 'Failed to open the camera.');return;}// 镜头状态回调this.onCameraStatusChange(this.cameraManager);// 监听CameraInput的错误事件this.onCameraInputChange(this.cameraInput, this.curCameraDevice);// 会话流程await this.sessionFlowFn(this.cameraManager, this.cameraInput, this.previewOutput, this.photoOutput);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `initCamera fail: ${err.code}`);}
    }
  1. 确定拍照输出流。通过CameraOutputCapability类中的photoProfiles属性,可获取当前设备支持的拍照输出流,通过cameraManager.createPhotoOutput方法创建拍照输出流。

    createPhotoOutputFn(cameraManager: camera.CameraManager,photoProfileObj: camera.Profile): camera.PhotoOutput | undefined {let photoOutput: camera.PhotoOutput | undefined = undefined;try {photoOutput = cameraManager.createPhotoOutput(photoProfileObj);Logger.info(TAG, `createPhotoOutputFn success: ${photoOutput}`);} catch (error) {let err = error as BusinessError;Logger.error(TAG, `createPhotoOutputFn failed: ${err.code}`);}return photoOutput;
    }
  2. 触发拍照。通过photoOutput类的capture方法,执行拍照任务。该方法有两个参数,第一个参数为拍照设置参数的setting,setting中可以设置照片的质量和旋转角度,第二参数为回调函数。具体代码如下所示:

    async takePicture(): Promise<void> {Logger.info(TAG, 'takePicture start');let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');let photoSettings: camera.PhotoCaptureSetting = {quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,mirror: cameraDeviceIndex ? true : false};await this.photoOutput?.capture(photoSettings);Logger.info(TAG, 'takePicture end');
    }
  3. 设置拍照photoAvailable的回调来获取Photo对象,点击拍照按钮,触发此回调函数,调用getComponent方法根据图像的组件类型从图像中获取组件缓存ArrayBuffer,使用createImageSource方法来创建图片源实例,最后通过createPixelMap获取PixelMap对象。注意:如果已经注册了photoAssetAvailable回调,并且在Session开始之后又注册了photoAvailable回调,会导致流被重启。不建议开发者同时注册photoAvailable和photoAssetAvailable。

    photoOutput.on('photoAvailable', (err: BusinessError, photo: camera.Photo) => {Logger.info(TAG, 'photoAvailable begin');if (photo === undefined) {Logger.error(TAG, 'photo is undefined');return;}let imageObj: image.Image = photo.main;imageObj.getComponent(image.ComponentType.JPEG, (err: BusinessError, component: image.Component) => {Logger.info(TAG, `getComponent start`);if (component === undefined) {Logger.error(TAG, 'getComponent failed');return;}let buffer: ArrayBuffer = component.byteBuffer;let imageSource: image.ImageSource = image.createImageSource(buffer);imageSource.createPixelMap((err: BusinessError, pixelMap: image.PixelMap) => {if (!pixelMap) {return;}this.handleImageInfo(pixelMap);})})
    })

    以上代码中执行handleImageInfo函数来对PixelMap进行全局存储并跳转到预览页面。具体代码如下所示:

    handleSavePicture = (imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void => {Logger.info(TAG, 'handleSavePicture');this.setImageInfo(imageInfo);AppStorage.set<boolean>('isOpenEditPage', true);Logger.info(TAG, 'setImageInfo end');
    }setImageInfo(imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void {Logger.info(TAG, 'setImageInfo');GlobalContext.get().setObject('imageInfo', imageInfo);
    }
  4. 进入到预览界面,通过GlobalContext.get().getT<image.PixelMap>('imageInfo')方法获取PixelMap信息,并通过Image组件进行渲染显示。

    this.curPixelMap = GlobalContext.get().getT<image.PixelMap>('imageInfo');Image(this.curPixelMap).objectFit(ImageFit.Contain).width(Constants.FULL_PERCENT).height(Constants.EIGHTY_PERCENT)

分段式拍照:

分段式拍照是应用下发拍照任务后,系统将分多阶段上报不同质量的图片。在第一阶段,系统快速上报低质量图,应用通过on(type:'photoAssetAvailable',callback:AsyncCallback):void接口会收到一个PhotoAsset对象,通过该对象可调用媒体库接口,读取图片或落盘图片。在第二阶段,分段式子服务会根据系统压力以及定制化场景进行调度,将后处理好的原图回传给媒体库,替换低质量图。具体操作步骤如下所示:

由于分段是拍照和单段式拍照步骤1-步骤4相同,就不再进行赘述。

  1. 设置拍照photoAssetAvailable的回调来获取photoAsset,点击拍照按钮,触发此回调函数,然后执行handlePhotoAssetCb函数来完成photoAsset全局的存储并跳转到预览页面。注意:如果已经注册了photoAssetAvailable回调,并且在Session开始之后又注册了photoAvailable回调,会导致流被重启。不建议开发者同时注册photoAvailable和photoAssetAvailable。

    photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset) => {Logger.info(TAG, 'photoAssetAvailable begin');if (photoAsset === undefined) {Logger.error(TAG, 'photoAsset is undefined');return;}this.handlePhotoAssetCb(photoAsset);
    });

    以上代码中执行handleImageInfo函数来对photoAsset进行全局存储并跳转到预览页面。具体代码如下所示:

    handleSavePicture = (imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void => {Logger.info(TAG, 'handleSavePicture');this.setImageInfo(imageInfo);AppStorage.set<boolean>('isOpenEditPage', true);Logger.info(TAG, 'setImageInfo end');
    }setImageInfo(imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void {Logger.info(TAG, 'setImageInfo');GlobalContext.get().setObject('imageInfo', imageInfo);
    }
  2. 进入预览界面通过GlobalContext.get().getT<image.PixelMap>('imageInfo')方法获取PhotoAsset信息,执行requestImage函数中的photoAccessHelper.MediaAssetManager.requestImageData方法根据不同的策略模式,请求图片资源数据,这里的请求策略为均衡模式BALANCE_MODE, 最后分段式子服务会根据系统压力以及定制化场景进行调度,将后处理好的原图回传给媒体库来替换低质量图。具体代码如下所示:

    photoBufferCallback: (arrayBuffer: ArrayBuffer) => void = (arrayBuffer: ArrayBuffer) => {Logger.info(TAG, 'photoBufferCallback is called');let imageSource = image.createImageSource(arrayBuffer);imageSource.createPixelMap((err: BusinessError, data: image.PixelMap) => {Logger.info(TAG, 'createPixelMap is called');this.curPixelMap = data;});
    };requestImage(requestImageParams: RequestImageParams): void {try {class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler<ArrayBuffer> {onDataPrepared(data: ArrayBuffer, map: Map<string, string>): void {Logger.info(TAG, 'onDataPrepared begin');Logger.info(TAG, `onDataPrepared quality: ${map['quality']}`);requestImageParams.callback(data);Logger.info(TAG, 'onDataPrepared end');}};let requestOptions: photoAccessHelper.RequestOptions = {deliveryMode: photoAccessHelper.DeliveryMode.BALANCE_MODE,};const handler = new MediaDataHandler();photoAccessHelper.MediaAssetManager.requestImageData(requestImageParams.context, requestImageParams.photoAsset,requestOptions, handler);} catch (error) {Logger.error(TAG, `Failed in requestImage, error code: ${error.code}`);}
    }aboutToAppear() {Logger.info(TAG, 'aboutToAppear begin');if (this.photoMode === Constants.SUBSECTION_MODE) {let curPhotoAsset = GlobalContext.get().getT<photoAccessHelper.PhotoAsset>('imageInfo');this.photoUri = curPhotoAsset.uri;let requestImageParams: RequestImageParams = {context: getContext(),photoAsset: curPhotoAsset,callback: this.photoBufferCallback};this.requestImage(requestImageParams);Logger.info(TAG, `aboutToAppear photoUri: ${this.photoUri}`);} else if (this.photoMode === Constants.SINGLE_STAGE_MODE) {this.curPixelMap = GlobalContext.get().getT<image.PixelMap>('imageInfo');}
    }
  3. 将步骤6获取的PixelMap对象数据通过Image组件进行渲染显示。

    Image(this.curPixelMap).objectFit(ImageFit.Contain).width(Constants.FULL_PERCENT).height(Constants.EIGHTY_PERCENT)

总结

通过分段式拍照,确保低质量图可接受的基础上,加快了Shot2See的完成时延,同时第二段保证了高质量照片不损失图片效果,达到与系统相机一致的拍照质量。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

这篇关于HarmonyOS NEXT实战:“相机分段式拍照”性能提升实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1122618

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

黑神话,XSKY 星飞全闪单卷性能突破310万

当下,云计算仍然是企业主要的基础架构,随着关键业务的逐步虚拟化和云化,对于块存储的性能要求也日益提高。企业对于低延迟、高稳定性的存储解决方案的需求日益迫切。为了满足这些日益增长的 IO 密集型应用场景,众多云服务提供商正在不断推陈出新,推出具有更低时延和更高 IOPS 性能的云硬盘产品。 8 月 22 日 2024 DTCC 大会上(第十五届中国数据库技术大会),XSKY星辰天合正式公布了基于星

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10