ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HarmonyOS AR Engine深度估计实战——把毫米距离画成实时热力图

HarmonyOS AR Engine深度估计实战——把毫米距离画成实时热力图 HarmonyOS AR Engine深度估计实战——把毫米距离画成实时热力图打开实验页面后相机画面上会多出一层彩色网格。它不是滤镜每个小格都保存着手机到画面中物体的大致距离。手机对准近处木纹地面时中心距离是791 mm对准约 3 米外的区域后中心距离变成2.91 m。两次锁定都取得了784/784个有效抽样点。热力镜里的暖色区域代表近处绿色和蓝色逐步对应更远的区域画面变化和毫米距离能够互相对上。锁定场景中心距离抽样范围有效抽样高置信度中近距离木纹地面791 mm0.48–0.80 m784/78411%远距离区域2.91 m1.43–3.10 m784/7849%这里的“深度图”可以先理解为一张距离表普通照片的像素记录颜色深度图的像素记录距离。页面再把距离换成颜色才得到容易观察的“热力图”。锁定按钮只把当前画面停住数字不会被重新计算或美化。先看懂页面第一次进入页面先认下面五个信息中心距离准星附近一小块区域到手机的距离不是整张画面的平均值红色、黄色距离较近绿色、蓝色距离逐渐变远有效抽样成功读到距离的格子数量784/784表示 784 个格子都有值高置信度系统认为“这次距离更可靠”的格子比例它不是有效格子的比例。实际操作时只看两个变化就够了手机靠近物体中心数字会变小网格偏暖色手机对准远处中心数字会变大网格逐渐变成绿色或蓝色。实验准备本次运行环境如下项目实际环境设备HUAWEI Mate 60 Pro系统HarmonyOS 7.0SDKAPI 26工程ArkTS Phone 应用targetSdkVersion26必需权限CAMERA、ACCELEROMETER、GYROSCOPE必需能力SLAM、DEPTHAR 类型WORLD深度模式AUTOMATIC工程已经完成签名真机连接正常。应用入口先检查相机权限、SLAM 和 DEPTH三项全部满足后才显示可用的“实验 09实时深度热力镜”。权限声明沿用 AR Engine 工程的基础配置requestPermissions: [ { name: ohos.permission.CAMERA, reason: $string:permission_reason_camera, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.ACCELEROMETER, reason: $string:permission_reason_accelerometer, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.GYROSCOPE, reason: $string:permission_reason_gyroscope, usedScene: { abilities: [EntryAbility], when: inuse } } ]入口页在创建会话前做能力门禁private canStartDepth(): boolean { const depth: FeatureItem | undefined this.features.find( (item: FeatureItem) item.type arEngine.ARFeatureType.ARENGINE_FEATURE_TYPE_DEPTH ); return this.canStartAR() depth ! undefined depth.supported; } private startDepthEstimation(): void { if (!this.cameraGranted) { this.latestMessage 未获得相机权限不启动深度估计。; hilog.info(DOMAIN, TAG, DEPTH_GUARD camerafalse); return; } if (!this.canStartAR()) { this.latestMessage 当前设备不支持 SLAM不启动深度估计。; hilog.info(DOMAIN, TAG, DEPTH_GUARD slamfalse); return; } if (!this.canStartDepth()) { this.latestMessage 当前设备不支持深度估计不创建 AR 会话。; hilog.info(DOMAIN, TAG, DEPTH_GUARD depthfalse); return; } hilog.info(DOMAIN, TAG, DEPTH_GUARD cameratrue slamtrue depthtrue); this.showDepthEstimation true; }这张彩色网格是怎么来的这张原理图只说明数据处理过程不是真机结果。正文中的距离、热力图和日志均来自后面的真实运行记录。相机每更新一次画面就会产生一份ARFrame。可以把它理解为“带有 AR 数据的一帧相机画面”。页面对每一帧做下面五件事收到新的相机帧取得一张“距离表”和一张“可信程度表”把底层缓冲区转成 ArkTS 可以按下标读取的整数数组从256 × 256的原始数据中抽出28 × 28个格子并计算准星附近的代表距离距离决定颜色可信程度决定透明度最后用 Canvas 盖到相机画面上。第一步打开能返回距离的 AR 会话页面底层使用ARView显示真实相机画面上层使用 Canvas 画彩色网格和准星。两层叠在一起后既能看到现实环境也能看到距离变化。下面这段配置只打开“自动返回深度”和“自动对焦”。平面、语义和 Mesh 都关闭本次页面只处理距离数据。const scene: Scene await Scene.load(); const context new arViewController.ARViewContext(); this.callback new DepthEstimationCallback( (viewContext: arViewController.ARViewContext, timestamp: number) { this.handleFrameUpdate(viewContext, timestamp); } ); context.scene scene; context.callback this.callback; context.config { type: arEngine.ARType.WORLD, planeFindingMode: arEngine.ARPlaneFindingMode.DISABLED, powerMode: arEngine.ARPowerMode.NORMAL, semanticMode: arEngine.ARSemanticMode.NONE, poseMode: arEngine.ARPoseMode.GRAVITY, depthMode: arEngine.ARDepthMode.AUTOMATIC, meshMode: arEngine.ARMeshMode.DISABLED, focusMode: arEngine.ARFocusMode.AUTO }; await context.init(); this.arContext context;相机出帧速度很快没有必要每一帧都重新画 784 个格子。下面的判断把读取频率限制为每 250 ms 最多一次页面暂停、结果锁定、页面退出或上一轮还没读完时都不会重复读取。const DEPTH_SAMPLE_INTERVAL_MS: number 250; private handleFrameUpdate( context: arViewController.ARViewContext, timestamp: number ): void { const now: number Date.now(); if (this.isDestroyed || this.isPaused || this.evidenceLocked || this.samplingInFlight || now - this.lastSampleTime DEPTH_SAMPLE_INTERVAL_MS) { return; } this.lastSampleTime now; this.samplingInFlight true; this.sampleCurrentDepth(context).finally(() { this.samplingInFlight false; }); }第二步从当前画面读出距离和可信程度只有页面显示相机正在稳定跟踪时才读取数据。acquireDepthImage16Bits()返回每个位置的毫米距离acquireDepthConfidenceImage()返回同一位置的可信程度。两张图尺寸相同下标也一一对应。frame session.getFrame(); const camera: arEngine.ARCamera frame.getCamera(); this.updateTrackingStatus(camera.state, camera.stateReason); if (camera.state ! arEngine.ARTrackingState.TRACKING) { return; } depthImage frame.acquireDepthImage16Bits(); confidenceImage frame.acquireDepthConfidenceImage(); if (depthImage.planes.length 0 || confidenceImage.planes.length 0) { throw new Error(深度图或置信度图没有可读取的平面); } const depthPlane: arEngine.ImageComponent depthImage.planes[0]; const confidencePlane: arEngine.ImageComponent confidenceImage.planes[0];深度功能刚启动时普通相机画面往往已经出现但距离数据还没准备好。真机返回1009200001时页面把它显示成“正在准备”继续等待而不是立即判定失败const DEPTH_NOT_READY_CODE: number 1009200001; if (err.code DEPTH_NOT_READY_CODE) { this.pendingCount 1; this.guideText 深度数据正在准备保持镜头稳定并扫过有纹理的物体边缘; } else { this.errorCount 1; this.guideText 深度读取异常${err.code}; }最终真机返回的距离表大小为256 × 256。两轮测试分别等待约 7.4 秒和 7.7 秒才出现首份有效数据所以进入页面后短时间只看到相机并不异常。遇到的问题数组读错后出现竖向空带第一版页面虽然能画出颜色却出现了规则的竖向空带有效格子固定为448/784。问题不在相机而在数组读取方式。当时把ImageComponent.buffer当成一个个原始字节又使用rowStride和pixelStride跳着取值。但 ArkTS 转换完成后每个数组元素已经是一个完整数值再按字节步长跳读就会有一部分位置永远被跳过。处理方式是先把ArrayBuffer转成Int32Array然后用最普通的二维数组下标y * width x读取。简单说数组已经按数值展开不能再按字节跳着读。修订后的代码同时读取距离和可信程度并从中心5 × 5小区域取代表值private sampleDepthGrid( width: number, height: number, depthPlane: arEngine.ImageComponent, confidencePlane: arEngine.ImageComponent ): DepthSampleResult { const depthValues: Int32Array new Int32Array(depthPlane.buffer); const confidenceValues: Int32Array new Int32Array(confidencePlane.buffer); const depths: number[] []; const confidences: number[] []; const centerValues: number[] []; let validCount: number 0; let highConfidenceCount: number 0; let minDepth: number 65535; let maxDepth: number 0; const centerColumn: number Math.floor(GRID_COLUMNS / 2); const centerRow: number Math.floor(GRID_ROWS / 2); for (let row: number 0; row GRID_ROWS; row 1) { const sourceY: number Math.min( height - 1, Math.floor((row 0.5) * height / GRID_ROWS) ); for (let column: number 0; column GRID_COLUMNS; column 1) { const sourceX: number Math.min( width - 1, Math.floor((column 0.5) * width / GRID_COLUMNS) ); const planeIndex: number sourceY * width sourceX; const depth: number depthValues[planeIndex]; const confidence: number confidenceValues[planeIndex]; depths.push(depth); confidences.push(confidence); if (depth 0) { validCount 1; minDepth Math.min(minDepth, depth); maxDepth Math.max(maxDepth, depth); if (confidence 2) { highConfidenceCount 1; } if (Math.abs(column - centerColumn) CENTER_RADIUS Math.abs(row - centerRow) CENTER_RADIUS) { centerValues.push(depth); } } } } return { depths: depths, confidences: confidences, validCount: validCount, highConfidenceCount: highConfidenceCount, minDepth: validCount 0 ? minDepth : 0, maxDepth: maxDepth, centerDepth: this.median(centerValues) }; }修订后有效抽样从448/784恢复到784/784竖向空带消失。rowStride和pixelStride仍保留在日志中作为原始信息但不再用来计算数组下标。中心距离没有只取准星下的一个点而是收集中心5 × 5区域的有效值再取排在中间的数。这样某一个点突然为空或跳得很远时页面数字不会立刻跟着乱跳。private median(values: number[]): number { if (values.length 0) { return 0; } const ordered: number[] values.slice().sort( (first: number, second: number) first - second ); const middle: number Math.floor(ordered.length / 2); if (ordered.length % 2 1) { return ordered[middle]; } return Math.round( (ordered[middle - 1] ordered[middle]) / 2 ); }第三步把毫米距离换成颜色页面固定绘制28 × 28也就是 784 个格子。距离决定颜色可信程度只决定颜色是更实还是更淡private depthColor(depth: number, confidence: number): string { if (depth 0) { return rgba(38, 49, 62, 0.20); } const alpha: string confidence 2 ? 0.88 : (confidence 1 ? 0.58 : 0.30); if (depth 700) { return rgba(255, 117, 92, ${alpha}); } if (depth 1500) { return rgba(255, 209, 90, ${alpha}); } if (depth 3000) { return rgba(93, 225, 168, ${alpha}); } return rgba(85, 181, 255, ${alpha}); }Canvas 每次清空旧画面再按行列绘制全部色块for (let index: number 0; index result.depths.length; index 1) { const row: number Math.floor(index / GRID_COLUMNS); const column: number index % GRID_COLUMNS; this.heatmapContext.fillStyle this.depthColor( result.depths[index], result.confidences[index] ); this.heatmapContext.fillRect( lensX column * cellWidth, lensY row * cellHeight, Math.max(1, cellWidth - 0.35), Math.max(1, cellHeight - 0.35) ); }修订版的实时画面已经没有规则空带中心距离为 1.52 m范围为 0.91–2.44 m784 个网格全部取得有效深度。这里最容易看错的是“高置信度”。例如高置信度为 11%不代表只有 11% 的格子有距离。本次仍然是784/784个格子有效只是其中 11% 被系统标为更可信其他格子照样显示只是颜色更淡。跟着真机做一遍1. 启动深度实验进入实验 09 后先拿着手机缓慢扫过木纹、家具边缘或明暗变化明显的区域。需要移动的是手机不是在屏幕上滑动。页面开始时显示“等待深度”出现彩色网格、中心距离和784/784后说明距离数据已经可用。2. 锁定中近距离结果把准星对准 0.4–1 米内的木纹地面。等中心数字不再大幅跳动后点击“锁定当前深度证据”。按钮变成“继续实时采样”说明当前画面已经冻结。第 204 帧结果为DEPTH_EVIDENCE_LOCK sample204 centerMm791 minMm483 maxMm804 valid784 highConfidenceRatio11这行日志翻译成页面数据就是中心 791 mm画面范围约 0.48–0.80 m784 个格子全部有效其中 11% 为高置信度。锁定只停止页面采样不会关闭相机。3. 锁定远距离结果点击“继续实时采样”把手机对准更远区域。等中心距离稳定在米级后再次锁定本次记录为DEPTH_EVIDENCE_LOCK sample159 centerMm2906 minMm1431 maxMm3104 valid784 highConfidenceRatio9这行日志表示中心 2906 mm画面范围约 1.43–3.10 m784 个格子仍全部有效。两次中心距离相差 2115 mm页面也从暖色为主变成绿色、蓝色为主数字和颜色变化能够互相验证。暂停、恢复和销毁远距离证据锁定后先恢复实时采样再点击“暂停深度”。页面停在第 274 帧等待 5 秒后没有出现新的DEPTH_SAMPLE。点击“恢复深度”后日志先记录从第 274 帧恢复随后采样增长到第 280、288、296 帧页面重新显示“实时深度”。DEPTH_SESSION_PAUSE sample274 DEPTH_SESSION_RESUME sample274 lockedfalse DEPTH_SAMPLE sample280 image256x256 format4 valid784 highConfidenceRatio9 centerMm2906 minMm1431 maxMm3104点击“销毁退出”后采样停在第 326 帧错误计数为 0页面返回实验工作台。DEPTH_SESSION_DESTROY sample326 pending19 errors0 lockedfalse深度图、置信度图和 AR 帧都在finally中释放。即使读取深度时抛出异常也不会跳过释放路径。} finally { if (confidenceImage) { await confidenceImage.release(); } if (depthImage) { await depthImage.release(); } if (frame) { await frame.release(); } }页面退出时销毁ARViewContext并清空 Canvasprivate async destroyARView(): Promisevoid { if (!this.arContext || this.isDestroyed) { return; } this.isDestroyed true; const context: arViewController.ARViewContext this.arContext; this.arContext undefined; await context.destroy(); if (this.canvasReady) { this.heatmapContext.clearRect( 0, 0, this.heatmapContext.width, this.heatmapContext.height ); } }构建并确认不是演示数据静态回归检查覆盖以下内容DEPTH 能力门禁自动深度和自动对焦配置深度图、置信度图的Int32Array转换y * width x平面索引中心区域中位数Canvas 热力图锁定、暂停、恢复和销毁日志深度图、置信度图和 AR 帧释放随机或模拟深度数据为 0 命中。执行结果AR Engine 深度估计静态回归检查通过。随后执行 clean 签名构建hvigorw clean assembleHap--no-daemon本次结果CompileArkTS SUCCESS PackageHap SUCCESS SignHap SUCCESS BUILD SUCCESSFUL in 17 s 410 ms最终签名 HAP 大小为 2,780,410 bytes覆盖安装和 Ability 启动均成功。最后得到的结果验证项实际结果CAMERA、SLAM、DEPTH 门禁通过深度图与置信度图成功取得深度图尺寸256 × 256热力图网格28 × 28784 个格子修订版有效抽样784/784中近距离中心值791 mm远距离中心值2906 mm两组中心差值2115 mm暂停第 274 帧停止增长恢复从第 274 帧恢复并继续出帧销毁第 326 帧销毁errors0这次案例完成的是一条完整的真实深度链路AR 会话取得 16 位毫米深度和置信度ArkTS 按官方方式转换数组固定网格抽样并计算中心中位数Canvas 把结果画到真实相机画面上最后用近、远两组数据和完整生命周期做了验证。官方资料AR Engine Kit 指南获取深度估计信息ArkTSArrayBuffer 数据类型转换说明
返回列表