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

news2024/11/15 18:43:58

概述

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

  • 分段式拍照是系统相机开发的重要功能之一,即相机拍照可输出低质量图用作缩略图,提升用户感知拍照速度,同时使用高质量图保证最后的成图质量达到系统相机的水平,构筑相机性能竞争力。这样可以优化系统的拍照响应时延,从而提升用户的体验。
  • 单段式拍照是在拍照过程中通过多帧融合以及多个底层算法仅会返回一张高质量图片,这样导致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开发必备

​​

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089758.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

几款最新好用的图纸加密软件

在现代数字化办公环境中&#xff0c;图纸的保护变得尤为重要。无论是建筑设计图纸、工程图纸&#xff0c;还是机械制造图纸&#xff0c;如何有效地加密并保护这些机密文件&#xff0c;避免信息泄露&#xff0c;是每个企业都需要重视的问题。今天&#xff0c;我们就来推荐几款最…

从开题到答辩:ChatGPT超全提示词分享!(下)【建议收藏】

数据收集 1. "请帮我找出关于如何收集【研究领域】社交媒体数据进行消费者行为研究的五篇指导性文章&#xff0c;并概述它们的主要方法论摘要。" 2. "我需要对【特定领域】市场的消费者偏好进行调查。能否提供一份包含调查问卷设计原则和示例的草稿&#xff1f;…

react vant 在使用dialog.confirm取消报错 Uncaught (in promise) undefined

项目场景&#xff1a; 在使用react做移动端开发时&#xff0c;需要使用Dialog.confirm确认框来做弹框选项&#xff0c;这是在操作中非常常用的一种场景。 问题描述 在列表中&#xff0c;使用弹框时&#xff0c;点击取消时&#xff0c;语法报错&#xff1b;导致后面再触发弹框…

养老小程序源码家政服务小程序开发方案

预约上门养老小程序&#xff0c;是php开发预约&#xff0c;前端是uniapp&#xff0c;有开发好的小程序案例&#xff0c;可源码&#xff0c;也可以二开&#xff0c;也可以定制开发。 一 用户端&#xff1a;服务分类、服务内容详情介绍、在线下单支付&#xff0c;管理我的订单。…

认知杂谈42

今天分享 有人说的一段争议性的话 I I 《摆脱自负自卑&#xff0c;找准自我定位》 I 在咱的生活里啊&#xff0c;有时候咱会在自负和自卑这两个地方来回晃悠&#xff0c;根本就找不着真正属于自己的那个位置。你想想看&#xff0c;自负的时候呢&#xff0c;就好像给自己戴了…

Unity(2022.3.41LTS) - 地形

目录 一、地形的创建 二.页面详解 1.创建相邻的 Terrain 瓦片。 2.雕刻和绘制地形。 3.添加树。 4.添加细节&#xff0c;如草地、花朵和岩石。 5.更改所选 Terrain 的常规设置 三、地形编辑工具 四、地形的属性设置 五、地形的优化 六、地形的应用场景 一、地形的创…

校园牛奶订购配送小程序开发制作方案

校园牛奶订购配送小程序系统的开发方案&#xff0c;包括对用户需求的分析、目标用户的界定、使用场景的设定以及开发功能模块的规划。校园牛奶订购配送小程序系统主要是为校园内学生和教职工提供牛奶订购与配送服务。 目标用户 主要面向在校学生、教职工以及其他有牛奶订购需求…

Mac GIF录制神器LICEcap

GIF录制软件的优点先看下 mac gif制作win gif录制完全免费界面简洁软件大小不到1M 今天就来介绍一款录屏并能生成 GIF 的软件&#xff1a;LICEcap。 背景 希望小巧免费的GIF录制的话LICEcap非常适合&#xff0c;网上很多能轻松录制屏幕的工具&#xff0c;基本都是录制之后带…

操作系统:实验四进程调度实验

一、实验目的 1、了解操作系统CPU管理的主要内容。 2、加深理解操作系统管理控制进程的数据结构--PCB。 3、掌握几种常见的CPU调度算法&#xff08;FCFS、SJF、HRRF、RR&#xff09;的基本思想和实现过程。 4、用C语言模拟实现CPU调度算法。 5、掌握CPU调度算法性能评价指…

基于python文案转语音并输出-自媒体等职业副业均可使用,不受他人限制

开发背景: 目前自媒体比较火爆,有很多书单、视频等推广方式可以作为副业盈利,之前每次搞的时候都需要不停的网上找一些在线文字转语音的平台将文案复制上去然后生成下载,好多还是付费的,挺无奈的,然后就想着自己能不能搞,然后的然后就有了下面的东西, 如果大家有此类需要…

文心智能体-梦想目标实现助手-实现你的老板梦

前言&#xff1a; 其实我从小就很羡慕小说里面的男主&#xff0c;从家境贫寒到后面成为天之骄子&#xff0c;在一路上都有很多好的机遇和贵人。用今天的话来说&#xff0c;男主好像都有一个“系统”&#xff0c;毫不意外&#xff0c;我也有这样的武侠梦&#xff0c;金庸的小说更…

波导阵列天线学习笔记6 用于K和Ka频段卫星通信的超宽带双圆极化波导阵列天线

摘要: 在本文中&#xff0c;设计了一种用于K和Ka双频段的宽带双圆极化波导天线阵列。一种多级方波导结构被利用&#xff08;exploited&#xff09;在辐射层内来实现双极化响应的激励。一种脊波导极化器被集成在内来实现左旋圆极化和右旋圆极化。为了馈网的更好设计&#xff0c;…

qtlinux

filezilla传 白色 权限不够 chmod x ./运行 source路径 qmake make 55可执行文件 nfs拷贝到开发版 ./运行 make j 核数 &#xff08;加速编译过程&#xff09;

【精选】推荐4款写作效率翻倍的AI论文写作助手

在当前的学术研究和写作领域&#xff0c;AI论文写作助手已经成为提高写作效率和质量的重要工具。这些工具利用先进的自然语言处理和机器学习技术&#xff0c;帮助研究人员和学生快速生成论文草稿、优化内容、进行查重和排版等操作。以下是四款高效且广受好评的AI论文写作助手&a…

迎来“成人礼”的良品铺子,蜕变了吗?

成立18年的良品铺子&#xff0c;正在迎来一场“成人礼”。 在这一关键节点&#xff0c;“苦”可能是其最先品尝到的味道。据良品铺子近日发布的财报&#xff0c;2024年上半年&#xff0c;公司实现营业收入38.86亿元&#xff0c;同比下滑2.52%&#xff1b;归属于上市公司股东的…

python脚本如何用sleep

Python 编程中使用 time 模块可以让程序休眠&#xff0c;具体方法是time.sleep(秒数)&#xff0c;其中“秒数”以秒为单位&#xff0c;可以是小数&#xff0c;0.1秒则代表休眠100毫秒。 代码如下&#xff1a; # 例1&#xff1a;循环输出休眠1秒 import time i 1 while i <…

Linux Debian12安装flameshot火焰截图工具

一、Linux Debian12安装flameshot 打开终端&#xff0c;运行&#xff1a; sudo apt install flameshot安装成功后&#xff0c;使用下面命令查看帮助信息&#xff1a; flameshot -h其中flameshot launcher命令可以打开启动器。 二、使用flameshot截图方法 打开终端&#x…

记录使用DevExpress的过程遇到问题

vs 2022 版本 DevExpress 版本 24.1 先参考这个网站去下载DevExpress和PatchDevExpress 24.1 版本使用 barManager1 使用过程&#xff1a; 1.菜单栏 默认经典样式 &#xff1a; 1.1 添加下拉菜单&#xff1a; 按照自己的需求去添加 如果有选择 barCheckItem1 复选框的控…

关于欧洲玩家的几个事实

欧洲游戏玩家是一个多元化和复杂的受众&#xff0c;受到广泛的文化、语言和社会因素的影响。他们的游戏偏好和行为在整个欧洲大陆上差异很大&#xff0c;反映了定义欧洲的丰富的民族认同和地区差异。 欧洲游戏玩家最显著的特征之一是他们对本地化内容的偏好。仅在欧盟就有二十…

5分钟部署Prometheus+Grafana批量监控Linux服务器

文章目录 一键安装Node Exporter安装prometheus创建数据存储目录创建配置文件下载运行Prometheus 安装Grafana创建数据目录下载运行Grafana配置Grafana监控Linux服务器登录首次登录后设置密码添加数据源选择prometheus填写prometheus地址导入模板 最近开始公众号文章也开始同步…