尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

HarmonyOS AR Engine 物体形状实战:把实体书识别为 RECTANGLE,再放进地面 AR 盒子

HarmonyOS AR Engine 物体形状实战:把实体书识别为 RECTANGLE,再放进地面 AR 盒子 上图根据本次真机实验的书籍、开口盒子和地面锚定关系制作用于展示最终效果。后文保留未经生成式处理的真机截图和原生日志作为验证证据。这次实操在 Mate 60 Pro 上完成了两项可以直接看到结果的 AR 操作AR Engine 的ARTarget把一本平放的实体书自动分类为RECTANGLE在真实水平平面上创建锚点显示一个顶部敞开的半透明立体盒子把实体书放进盒子后确认匹配完成。真机在第 766、780 和 810 帧三次取得有效矩形结果。第一次成功时书本的包围盒尺寸为0.2219 × 0 × 0.2976 m包围球半径为0.1856 mOBJECT_SNAPSHOT sample766 camera0 total3 tracking1 paused2 stopped0 acquireErrors0 stateErrors0 classified1 unknown0 cube0 circle0 rectangle1 shape3 radius0.1856 size0.2219,0.0000,0.2976 center-0.0727,-0.8548,-0.4059 shapeStatus0 geometryStatus0本机 SDK 中shape3对应ARENGINE_TARGET_SHAPE_RECTANGLE。shapeStatus0和geometryStatus0表示形状、包围盒、半径及中心位姿均读取成功。最初版本使用扁平书籍模型地面命中距离为0.6598 m。为了让放入动作更直观后续把锚点上的几何体改为28 × 21 × 6 cm开口盒子内部保留24 × 17 cm的书籍框。开口盒子复测的命中距离为0.7255 m锚点状态为TRACKINGBOOK_GUIDE_PLACE modelOPEN_BOX_GUIDE placed1 hitStatus0 hitCount2 anchorTracking0 distance0.7255 center-0.3791,-0.8131,0.0138实体书放入开口盒子后本轮确认记录写在第 6946 帧BOOK_MATCH_CONFIRM confirmed1 manualtrue sample6946 camera0 anchor0 status0这里有两个独立结果RECTANGLE是单独扫描实体书时由 AR Engine 自动返回的形状分类“书籍摆放匹配成功”是锚点稳定时由操作者确认实体书已经放入盒子。人工确认不会修改ARTarget的 shape、计数或几何数据。本次实验的完整过程整个过程分为四步扫描水平平面、创建盒子锚点、放入实体书、读取官方形状结果。应用中同时保留两条数据链路地面扫描 → HitTest → ARAnchor → OpenGL 开口盒子 → 人工确认 相机帧 → ARTarget → TrackingState → ShapeType → AABB / Radius / CenterPose第一条链路负责可见的 AR 摆放效果第二条链路负责官方物体形状分类。把它们放在同一个实验页里既能看到 AR 模型也能判断ARTarget到底有没有返回有效形状。实操环境与准备项目本次实操环境开发工具DevEco Studio开发语言ArkTS CSDKHarmonyOS API 26测试设备HUAWEI Mate 60 Pro设备系统HarmonyOS 7.0AR 会话类型ARENGINE_TYPE_WORLD平面模式水平与垂直平面语义模式ARENGINE_SEMANTIC_MODE_TARGET深度模式ARENGINE_DEPTH_MODE_AUTOMATIC对焦模式ARENGINE_FOCUS_MODE_AUTO必需权限CAMERA、ACCELEROMETER、GYROSCOPE必需能力SLAM、SEMANTIC测试物体选择了一本封面颜色鲜明、边缘完整的平装书。书本平放后轮廓接近矩形封面有足够纹理移动手机时也容易保持完整取景。入口页先完成相机权限和设备能力检测。CAMERA、SLAM 或 SEMANTIC 条件不满足时不进入原生 AR 会话。权限声明如下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 } } ]AR Engine 物体形状使用 C API。本次通过 Native XComponent 显示真实相机画面和 OpenGL 模型再通过 NAPI 把形状、几何与锚点状态传给 ArkTS 页面。CMake 配置如下cmake_minimum_required(VERSION 3.5.0) project(ObjectSemanticLab) set(CMAKE_CXX_STANDARD 17) find_library(ARENGINE_LIB libarengine_ndk.z.so) add_library(objectsemantic SHARED object_semantic.cpp) target_link_libraries(objectsemantic PUBLIC EGL GLESv3 hilog_ndk.z ace_ndk.z ace_napi.z ${ARENGINE_LIB} )创建 TARGET 语义会话会话使用 WORLD 类型同时开启平面检测、TARGET 语义、自动深度和自动对焦。AREngine_ARConfig*confignullptr;HMS_AREngine_ARConfig_Create(session,config);HMS_AREngine_ARConfig_SetPreviewSize(session,config,1440,1080);HMS_AREngine_ARConfig_SetUpdateMode(session,config,ARENGINE_UPDATE_MODE_LATEST);HMS_AREngine_ARConfig_SetFocusMode(session,config,ARENGINE_FOCUS_MODE_AUTO);HMS_AREngine_ARConfig_SetARType(session,config,ARENGINE_TYPE_WORLD);HMS_AREngine_ARConfig_SetPlaneFindingMode(session,config,ARENGINE_PLANE_FINDING_MODE_HORIZONTAL_AND_VERTICAL);HMS_AREngine_ARConfig_SetSemanticMode(session,config,ARENGINE_SEMANTIC_MODE_TARGET);HMS_AREngine_ARConfig_SetDepthMode(session,config,ARENGINE_DEPTH_MODE_AUTOMATIC);AREngine_ARStatus configureStatusHMS_AREngine_ARSession_Configure(session,config);HMS_AREngine_ARConfig_Destroy(config);配置完成后回读语义和对焦模式并把每个调用的状态写入日志。本次所有状态均为 0OBJECT_CONFIG preview0 update0 focusSet0 focusGet0 focusMode1 type0 plane0 semanticSet0 semanticGet0 semanticMode2 depth0 configure0 frame0 texture0 geometry0 resume0本机 SDK 中semanticMode2为 TARGETfocusMode1为 AUTO。查询 ARTarget先看跟踪状态每一帧先查询ARENGINE_TRACKABLE_TARGET再读取每个目标的跟踪状态。AREngine_ARTrackableList*targetListnullptr;HMS_AREngine_ARTrackableList_Create(session,targetList);HMS_AREngine_ARSession_GetAllTrackables(session,ARENGINE_TRACKABLE_TARGET,targetList);int32_ttargetSize0;HMS_AREngine_ARTrackableList_GetSize(session,targetList,targetSize);for(int32_tindex0;indextargetSize;index){AREngine_ARTrackable*trackablenullptr;AREngine_ARStatus acquireStatusHMS_AREngine_ARTrackableList_AcquireItem(session,targetList,index,trackable);if(acquireStatus!ARENGINE_SUCCESS||trackablenullptr){acquireErrors1;continue;}AREngine_ARTrackingState stateARENGINE_TRACKING_STATE_STOPPED;AREngine_ARStatus stateStatusHMS_AREngine_ARTrackable_GetTrackingState(session,trackable,state);if(stateStatus!ARENGINE_SUCCESS){stateErrors1;HMS_AREngine_ARTrackable_Release(trackable);continue;}if(stateARENGINE_TRACKING_STATE_PAUSED){paused1;HMS_AREngine_ARTrackable_Release(trackable);continue;}if(stateARENGINE_TRACKING_STATE_STOPPED){stopped1;HMS_AREngine_ARTrackable_Release(trackable);continue;}// 只有 TRACKING 目标才进入形状与几何读取。}首轮扫描时已经发现两个候选目标但两者一直处于PAUSEDOBJECT_SNAPSHOT sample82 camera0 total2 tracking0 paused2 stopped0 acquireErrors0 stateErrors0 classified0 unknown0 cube0 circle0 rectangle0 shape0这时相机已经是TRACKING目标列表也能取得数据但还不能读取有效形状。页面因此显示“候选目标未进入跟踪”没有把total2写成识别成功。读取形状、包围盒、半径和中心位姿目标进入TRACKING后将通用 Trackable 转为AREngine_ARTarget读取形状类型。auto*targetreinterpret_castAREngine_ARTarget*(trackable);AREngine_ARTargetShapeLabel shapeARENGINE_TARGET_SHAPE_UNKNOWN;AREngine_ARStatus shapeStatusHMS_AREngine_ARTarget_GetShapeType(session,target,shape);if(shapeStatusARENGINE_SUCCESSshape!ARENGINE_TARGET_SHAPE_UNKNOWN){classified1;}else{unknown1;}if(shapeARENGINE_TARGET_SHAPE_CUBE){cube1;}elseif(shapeARENGINE_TARGET_SHAPE_CIRCLE){circle1;}elseif(shapeARENGINE_TARGET_SHAPE_RECTANGLE){rectangle1;}几何数据使用三个接口分别读取floataabb[6]{};floatradius0.0f;floatposeRaw[7]{};AREngine_ARPose*posenullptr;AREngine_ARStatus aabbStatusHMS_AREngine_ARTarget_GetAxisAlignedBoundingBox(session,target,aabb,6);AREngine_ARStatus radiusStatusHMS_AREngine_ARTarget_GetRadius(session,target,radius);HMS_AREngine_ARPose_Create(session,nullptr,0,pose);AREngine_ARStatus centerStatusHMS_AREngine_ARTarget_GetCenterPose(session,target,pose);AREngine_ARStatus rawStatusHMS_AREngine_ARPose_GetPoseRaw(session,pose,poseRaw,7);HMS_AREngine_ARPose_Destroy(pose);floatsizeXstd::abs(aabb[3]-aabb[0]);floatsizeYstd::abs(aabb[4]-aabb[1]);floatsizeZstd::abs(aabb[5]-aabb[2]);书本平放在水平面上因此本次 AABB 的一个轴接近 0另外两个轴约为 22 cm 和 30 cm与画面中的实体书尺寸吻合。在地面创建盒子锚点页面中央显示十字准星。点击“在这里放置开口盒子”时原生层在准星附近执行 HitTest。AREngine_ARHitResultList*hitListnullptr;HMS_AREngine_ARHitResultList_Create(session,hitList);floatpixelXstatic_castfloat(width)*0.5f;floatpixelYstatic_castfloat(height)*0.48f;AREngine_ARStatus hitStatusHMS_AREngine_ARFrame_HitTest(session,frame,pixelX,pixelY,hitList);命中结果不会直接使用。候选对象还要同时满足四个条件Trackable 类型为ARENGINE_TRACKABLE_PLANETrackable 状态为ARENGINE_TRACKING_STATE_TRACKING平面方向为ARENGINE_PLANE_FACING_HORIZONTAL_UPWARD命中 Pose 位于平面多边形内部。boolvalidPlanetrackableTypeARENGINE_TRACKABLE_PLANEtrackableStateARENGINE_TRACKING_STATE_TRACKINGplaneTypeARENGINE_PLANE_FACING_HORIZONTAL_UPWARDposeStatusARENGINE_SUCCESSpolygonStatusARENGINE_SUCCESSinPolygon!0;if(validPlane){AREngine_ARAnchor*anchornullptr;AREngine_ARStatus anchorStatusHMS_AREngine_ARHitResult_AcquireNewAnchor(session,hit,anchor);AREngine_ARTrackingState anchorStateARENGINE_TRACKING_STATE_STOPPED;HMS_AREngine_ARAnchor_GetTrackingState(session,anchor,anchorState);}正式放置前对着有纹理的地面缓慢移动手机让十字落在无遮挡区域。成功会话返回两个命中候选最终使用的锚点处于TRACKING距离相机0.6598 m。用 OpenGL 绘制顶部敞开的盒子盒子不是图片覆盖而是原生 OpenGL 几何体。外框尺寸为0.28 × 0.21 × 0.06 m内部书籍框为0.24 × 0.17 m。顶部不绘制面因此可以清楚看到实体书位于盒子内部。constexprfloatBOX_OUTER_WIDTH_METERS0.28f;constexprfloatBOX_OUTER_LENGTH_METERS0.21f;constexprfloatBOX_WALL_HEIGHT_METERS0.06f;constexprfloatBOOK_SLOT_WIDTH_METERS0.24f;constexprfloatBOOK_SLOT_LENGTH_METERS0.17f;每帧从锚点取得模型矩阵再读取相机 View 和 Projection 矩阵floatmodel[16]{};floatview[16]{};floatprojection[16]{};HMS_AREngine_ARAnchor_GetPose(session,guideAnchor,pose);HMS_AREngine_ARPose_GetMatrix(session,pose,model,16);HMS_AREngine_ARCamera_GetViewMatrix(session,camera,view,16);AREngine_ClipPlaneDistance clip{0.05f,20.0f};HMS_AREngine_ARCamera_GetProjectionMatrix(session,camera,clip,projection,16);autoviewModelMultiplyMatrix(view,model);automvpMultiplyMatrix(projection,viewModel.data());盒子使用一块半透明底板和四面竖直侧壁共 30 个三角形顶点顶部边框、底部边框、四条竖边、内部书籍框和中心十字使用不透明线段。等待放书时为青色确认完成后改成绿色glEnable(GL_DEPTH_TEST);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);glUseProgram(guideProgram);glUniformMatrix4fv(mvpUniform,1,GL_FALSE,mvp.data());if(matched){glUniform4f(colorUniform,0.20f,0.94f,0.65f,0.32f);}else{glUniform4f(colorUniform,0.16f,0.74f,1.0f,0.24f);}glDrawArrays(GL_TRIANGLES,0,30);glVertexAttribPointer(positionAttribute,3,GL_FLOAT,GL_FALSE,0,lineVertices);glDrawArrays(GL_LINES,0,36);盒子出现后移动手机观察它是否仍留在原地。锚点继续TRACKING时盒子不会跟着屏幕移动。在 ArkTS 页面完成三步交互Native 模块向 ArkTS 暴露启动、快照、放置、确认、重置和生命周期动作declare const objectSemantic: { start: (id: string, rotation: number) void; getSnapshot: (id: string) ObjectSemanticSnapshot; placeGuide: (id: string) void; confirmGuide: (id: string) void; resetGuide: (id: string) void; pause: (id: string) void; resume: (id: string) void; stop: (id: string) void; };页面根据guideState只保留一个主动作private runPrimaryAction(): void { if (this.snapshot.guideState 2) { objectSemantic.confirmGuide(this.componentId); hilog.info(DOMAIN, TAG, BOOK_UI_CONFIRM sample%{public}d manualtrue, this.snapshot.sample); return; } if (this.snapshot.guideState 3) { objectSemantic.resetGuide(this.componentId); return; } objectSemantic.placeGuide(this.componentId); }三个阶段对应的按钮文字为未放置在这里放置开口盒子 已锚定确认实体书已放入盒子 已完成重新开始匹配确认动作不调用图像分类也不写入官方 TARGET 结果。它只在相机和书籍锚点都处于TRACKING时记录人工完成状态boolconfirmedanchorStatusARENGINE_SUCCESSanchorStateARENGINE_TRACKING_STATE_TRACKINGcameraStateARENGINE_TRACKING_STATE_TRACKING;if(confirmed){snapshot.guideStateGUIDE_MATCHED;snapshot.guideManualConfirmed1;snapshot.guideConfirmedSamplesample;}真机怎么操作1. 先让书完整进入画面把书平放在有纹理的地面上手机距离书大约 6090 cm。取景时保留书本四周的一圈地面不要让封面边缘贴住画面边界。首轮近距离扫描时两个候选目标持续PAUSED没有有效形状。2. 缓慢移动手机不在屏幕上滑动保持书本完整入镜手机向左、右和斜上方缓慢移动让 AR Engine 取得不同视角。屏幕滑动不会改变相机位姿对物体跟踪没有帮助。本次移动到第 766 帧时第 3 个候选目标进入TRACKING形状变为RECTANGLE第 780 帧仍然保持相同尺寸第 810 帧再次取得矩形结果。sample766 tracking1 paused2 classified1 rectangle1 shape3 radius0.1856 size0.2219,0.0000,0.2976 sample780 tracking1 paused2 classified1 rectangle1 shape3 radius0.1856 size0.2219,0.0000,0.2976 sample810 tracking1 paused2 classified1 rectangle1 shape3 radius0.1841 size0.2240,0.0000,0.29213. 扫描地面并放置盒子让中心十字对准书本旁边的空地点击“在这里放置开口盒子”。成功时页面显示盒子已锚定原生日志同时给出命中候选数、距离、锚点状态和世界坐标。4. 把实体书放进盒子把书移入半透明盒子调整位置和方向让书位于内部24 × 17 cm落位框附近。确认实体书已经进入盒子后点击“确认实体书已放入盒子”。放入时页面显示锚点TRACKING · 稳定跟踪、命中距离0.73 m和盒子世界坐标点击确认后页面显示“书籍摆放匹配成功”盒子描边变为绿色并保留“人工确认”标记BOOK_GUIDE_PLACE modelOPEN_BOX_GUIDE placed1 hitStatus0 hitCount2 anchorTracking0 distance0.7255 center-0.3791,-0.8131,0.0138 BOOK_MATCH_CONFIRM confirmed1 manualtrue sample6946 camera0 anchor0 status0这一轮书和盒子组合进入 TARGET 视野后官方结果稳定返回CUBEAABB 为0.2081 × 0.0281 × 0.2726 m。它没有覆盖前面单独扫描书籍取得的RECTANGLE而是作为本轮组合外形的独立记录下面的图只用来区分两轮扫描对象左侧是单独扫描书籍右侧是书籍放入盒子后的组合目标。分类结论仍以真机截图和紧随其后的原生日志为准。OBJECT_SNAPSHOT sample5940 camera0 total4 tracking1 paused3 stopped0 classified1 cube1 rectangle0 shape1 radius0.1720 size0.2081,0.0281,0.2726 shapeStatus0 geometryStatus0 OBJECT_SNAPSHOT sample6960 camera0 total4 tracking1 paused3 stopped0 classified1 cube1 rectangle0 shape1 radius0.1720 size0.2081,0.0281,0.2726 shapeStatus0 geometryStatus0遇到的情况与处理情况一目标数量增加但一直没有形状最初页面能看到两个 TARGET却一直是PAUSED 2 / TRACKING 0。这说明目标候选已经产生形状读取接口也没有报错但目标尚未进入可读取形状的跟踪状态。处理时依次核对了三项配置平面检测不能关闭改为HORIZONTAL_AND_VERTICAL相机对焦从默认固定模式改为AUTO书本四条边不能贴住相机画面边缘扫描时保留周围地面。系统日志曾反复出现Invalid 2D box. (2D box lies along the image border)把整本书完整放进画面并缓慢移动后目标进入TRACKING随后取得RECTANGLE。情况二模型已经出现证据卡仍显示初始值第一版页面顶部已经显示“模型已锚定”相机画面也能看到立体模型但底部锚点卡仍显示“尚未创建”。原生日志中的锚点状态和距离是正确的问题出在 ArkUI 自定义 Builder。错误写法把动态值作为 Builder 参数传入this.evidenceRow(锚点状态, this.guideTrackingText())修改后只传固定标签在 Builder 内直接读取Statethis.evidenceRow(锚点状态) Builder private evidenceRow(label: string): void { Row() { Text(label) Text(this.evidenceValue(label)) } }修复后的待放置、命中失败和按钮状态均能随快照变化。情况三点击放置后命中结果为 0地面看起来平整不代表 AR Engine 已经建立可命中的平面。重新进入会话后立即点击原生返回BOOK_GUIDE_PLACE placed0 hitStatus-2001 hitCount0 anchorTracking-1 distance0.0000 center0.0000,0.0000,0.0000页面按失败分支显示“这次没有命中地面”不创建锚点也不显示模型。处理动作只有两个继续缓慢移动手机让画面看到更多地面纹理再让十字落在无遮挡区域重新放置。不能在hitCount0时直接生成一个假锚点。构建与验证结果静态回归脚本检查以下内容TARGET 会话保留水平与垂直平面检测自动对焦已经设置并回读PAUSED、STOPPED、Acquire 错误和状态错误均有统计HitTest、水平向上平面、锚点、盒子渲染和释放接口均存在开口盒子的外框、墙高、内部书籍框和OPEN_BOX_GUIDE日志标识均存在人工确认日志固定包含manualtrueArkTS 页面不会用人工确认覆盖官方 TARGET 结果。回归结果物体语义状态观测回归检查通过。最终执行 clean 签名构建hvigorw clean assembleHap--no-daemon构建结果BuildNativeWithCmake SUCCESS BuildNativeWithNinja SUCCESS CompileArkTS SUCCESS PackageHap SUCCESS SignHap SUCCESS BUILD SUCCESSFUL本次结果这次真机实验得到的结果如下验证项真机结果TARGET 配置成功semanticMode2官方形状分类单独扫描实体书成功返回RECTANGLE目标几何成功约0.222 × 0.298 m半径约0.186 m地面开口盒子成功28 × 21 × 6 cm命中距离0.7255 m锚点TRACKING实体书放入盒子成功真机画面可见书位于四面侧壁内部摆放确认成功第 6946 帧记录manualtrue官方形状分类书与盒子组合返回CUBEAABB0.2081 × 0.0281 × 0.2726 m自动分类与人工摆放确认是两条独立证据。单独扫描书籍得到RECTANGLE放入盒子后的组合外形得到CUBE人工确认不会决定或修改这两个枚举结果。目标在部分帧会从TRACKING回到PAUSED因此页面保留最后一次有效自动分类结果实时 TARGET 计数仍继续更新。退出页面时先 detach 并 release 书籍锚点再停止 Session销毁 Frame、两个 OpenGL Program、相机纹理、EGL Surface、EGL Context 和 Native XComponent 实例避免相机与原生资源留在后台。官方资料AR Engine Kit 指南识别目标形状C/CHarmonyOS AR Engine C 示例
返回列表